R-Version: [Default] [64-bit] C:\Program Files\R\R-4.1.0

Imports

In folgendem Junk werden alle Tabellen aus den CSV’s eingelesen. Doku Daten: https://sorry.vse.cz/~berka/challenge/PAST/index.html (rechte Seite PKDD’99 Challenge > Data > Financial Data Description)

Einlesen der Daten

Der Datensatz besteht aus acht verschiedenen Tabellen, welche teils durch Keys miteinander verknüpft sind.

root_path <- "./xselling_banking_data-1/xselling_banking_data/"

accounts <- read.csv(paste0(root_path, "account.csv"), header = TRUE, sep = ";")
cards <- read.csv(paste0(root_path, "card.csv"), header = TRUE, sep = ";")
clients <- read.csv(paste0(root_path, "client.csv"), header = TRUE, sep = ";")
dispositions <- read.csv(paste0(root_path, "disp.csv"), header = TRUE, sep = ";")
districts <- read.csv(paste0(root_path, "district.csv"), sep = ";")
loans <- read.csv(paste0(root_path, "loan.csv"), header = TRUE, sep = ";")
orders <- read.csv(paste0(root_path, "order.csv"), header = TRUE, sep = ";")
transactions <- read.csv(paste0(root_path, "trans.csv"), header = TRUE, sep = ";")

Cleaning

Accounts

sample_n(accounts, 5)

Die Account-Tabelle enthält vier Kolonnen: die Account-ID, die District-ID (welche auf die District-Tabelle verweist), die Frequenz, welche die Häufigkeit der Ausstellung der Abrechnungen als Kategorie besagt, und das Erstellungsdatum des Accounts. Die Frequenz kann eine von drei verschiedenen Werten annehmen.

unique(accounts$frequency)
[1] "POPLATEK MESICNE"   "POPLATEK PO OBRATU" "POPLATEK TYDNE"    

Nachfolgend sollen die Frequenz-Werte übersetzt und das Datum in ein richtiges Format transformiert werden. Ausserdem soll die Tabelle auf fehlende Werte überprüft werden.

accounts$date <- as.Date(as.character(accounts$date), format= "%y%m%d")

accounts$frequency[accounts$frequency == "POPLATEK MESICNE"]   <- "monthly"
accounts$frequency[accounts$frequency == "POPLATEK TYDNE"]     <- "weekly"
accounts$frequency[accounts$frequency == "POPLATEK PO OBRATU"] <- "after_transaction"

sum(is.na(accounts))
[1] 0

Es gibt also keine fehlende Werte in diesem Dataframe.

Cards

sample_n(cards, 5)

Auch bei card muss das Datum umgewandelt werden, der zeitliche Teil wird ignoriert, da er immer 0 ist.

cards$issued <- as.Date(as.character(cards$issued), format= "%y%m%d")

cards$type[cards$type == "gold"]     <- "classic"
cards <- filter(cards, type == "classic")

sum(is.na(cards))
[1] 0

Clients

sample_n(clients, 10)

In der Tabelle existiert die Spalte birth_number, welcher man auf den ersten Blick die Datumsräpresentation nicht ansieht. In der Doku wird die Struktur deutlich, sie ist für Männer YYMMDD und für Frauen YYMMDD+50DD. In Folge wird die Nummer in ihre Datumsräpresentation konvertiert und die Spalte “gender” als male/female aufgeschlüsselt. Zudem wird das Alter der clients bezogen auf das Jahr 1999 herausextrahiert, da der Datensatz aus diesem Jahr stammt. Es wird nicht year(Sys.Date()) verwendet, damit die Daten auch in Zukunft konsistent blieben.

# Months above 12 must be female
clients <- mutate(clients, gender = 
                     ifelse(substr(birth_number, 3, 4) > 12, "female", "male"))

# Substract the 50 to get the birth month
clients <- mutate(clients, birth_month =
                     ifelse(as.numeric(substr(birth_number, 3, 4)) > 12,
                            as.numeric(substr(birth_number, 3, 4)) - 50,
                            as.numeric(substr(birth_number, 3, 4))))

# Transform the birth_number to a date
clients <- mutate(clients, birth_number = paste("19",
                                                substr(birth_number, 1, 2), 
                                                str_pad(birth_month, 2,
                                                        pad = "0"),
                                                substr(birth_number, 5, 6),
                   sep = "", collapse = NULL))
clients$birth_date <- as.Date(as.character(clients$birth_number),
                               format= "%Y%m%d")

# Remove unused columns
clients$birth_month <- NULL
clients$birth_number <- NULL

# Get the age of the clients in the year 1999 and save it in a column
get_age <- function(birth_date) {
  base_year <- 99
  year <- substr(birth_date, 3, 4)
  result <- base_year - as.integer(year)
  
  return(result)
}
clients <- clients %>%
   mutate(age = get_age(birth_date))

sum(is.na(clients))
[1] 0
accounts
clients

Dispositions

sample_n(dispositions, 5)

Bei Dispositions sollen nur Owners verwendet werden, da die Analyse nur Eigentümer von Konten behandeln soll.

dispositions <- dispositions %>% filter(type == 'OWNER')

sum(is.na(dispositions))
[1] 0

Districts

Bei district sind die Spaltennamen der Tabelle abhanden gekommen. Hier werden die Tabellennamen umbenannt, gemäss Doku.

districts <- rename(districts, district_id = A1, district_name = A2, region = A3, 
                   inhabitants = A4, municipalities_inhabitants_smaller_499 = A5, 
                   municipalities_inhabitants_500_to_1999 = A6, 
                   municipalities_inhabitants_2000_to_9999 = A7, 
                   municipalities_inhabitants_larger_10000 = A8, cities = A9, 
                   urban_inhabitants_ratio = A10, average_salary = A11,
                   unemployment_rate_95 = A12, unemployment_rate_96 = A13,
                   entrepreneurs_per_1000 = A14, crimes_95 = A15,
                   crimes_96 = A16)

sum(is.na(districts))
[1] 0

Transactions

sample_n(transactions, 5)

In den Transaktionen muss das Datum gemäss Format YYMMDD konvertiert werden.

# Rename k_symbol
transactions <- rename(transactions, c("characterization" = "k_symbol")) 

# Change formats
transactions$date <- as.Date(as.character(transactions$date), format= "%y%m%d")
transactions$amount <- as.numeric(transactions$amount)
transactions$balance <- as.numeric(transactions$balance)

# Translate values
transactions$type[transactions$type == "PRIJEM"] <- "credit"
transactions$type[transactions$type == "VYDAJ"]  <- "withdrawal"
transactions$type[transactions$type == "VYBER"]  <- "withdrawal"

transactions$operation[transactions$operation == "VKLAD"]          <- "cash credit"
transactions$operation[transactions$operation == "PREVOD Z UCTU"]  <- "collection"
transactions$operation[transactions$operation == "VYBER"]          <- "cash withdrawal"
transactions$operation[transactions$operation == " "]              <- "unknown"
transactions$operation[transactions$operation == "PREVOD NA UCET"] <- "remittance"
transactions$operation[transactions$operation == "VYBER KARTOU"]   <- "card withdrawal"

transactions$characterization[transactions$characterization == " "] <- "unknown"
transactions$characterization[transactions$characterization == "DUCHOD"] <- "pension"
transactions$characterization[transactions$characterization == "UROK"] <- "interest"
transactions$characterization[transactions$characterization == "SIPO"] <- "household"
transactions$characterization[transactions$characterization == "SLUZBY"] <- "payment statement"
transactions$characterization[transactions$characterization == "POJISTNE"] <- "insurance"
transactions$characterization[transactions$characterization == "SANKC. UROK"]  <- "neg_interest"
transactions$characterization[transactions$characterization == "UVER"]  <- "loan_pay"

sum(is.na(transactions))
[1] 760931

Orders

sample_n(orders, 5)
# Rename column k_symbol
orders <- rename(orders, "characterization" = "k_symbol") 

# Translate column characterization
orders$characterization[orders$characterization == "SIPO"]     <- "household"
orders$characterization[orders$characterization == "UVER"]     <- "loan"
orders$characterization[orders$characterization == "POJISTNE"] <- "insurance"
orders$characterization[orders$characterization == "LEASING"]  <- "leasing"

# Categorize NA as unknown
orders$characterization[is.na(orders$characterization)] <- "unknown"

orders$amount <- as.numeric(orders$amount)

sum(is.na(loans))
[1] 0

Loans

sample_n(loans, 5)
loans$date <- as.Date(as.character(loans$date), format= "%y%m%d")
loans$payments <- as.numeric(loans$payments)
loans$amount <- as.numeric(loans$amount)

# Make column status human readable
loans$status[loans$status == "A"] <- "finished_payed"
loans$status[loans$status == "B"] <- "finished_not_payed"
loans$status[loans$status == "C"] <- "running_ok"
loans$status[loans$status == "D"] <- "running_in_debt"

sum(is.na(loans))
[1] 0

Zusammenfügen der Dataframes

In diesem Abschnitt werden die verschiedenen Tabellen zusammen gesetzt. Dabei werden loan, cards und district it left join angehängt, damit fehlende Spalten nicht den Datensatz verkleinern. Die Transaktionsdaten werden hier noch nicht zusammengeführt.

# Clients mit dispositions
full <- inner_join(clients, dispositions, by = "client_id", suffix = c(".client", ".dispositions"))
sum(duplicated(full$client_id))
[1] 0
# Full mit account
full <- inner_join(full, accounts, by = "account_id", suffix = c("", ".accounts"))
sum(duplicated(full$account_id))
[1] 0
# Full mit loan
sum(duplicated(loans$account_id))
[1] 0
full <- left_join(full, loans, by = "account_id", suffix = c("", ".loans"))

# Full mit cards
full <- left_join(full, cards, by = "disp_id", suffix = c("", ".cards"))
sum(duplicated(cards$disp_id))
[1] 0
# District Informations for client
full <- left_join(full, districts, by = "district_id")

# District informations for card
full <- left_join(full, districts, by = c("district_id.accounts"="district_id"), suffix = c("", ".accounts"))

sample_n(full, 5)

Jugendliche und Personen, welche während des Zeitraums des Datensatzes erst erwachsen worden sind, sollen nicht in die Auswertung einfliessen. Da sicher der Datensatz über einen Zeitraum von sechs Jahren erstreckt werden alle Clients jünger als 25 Jahre herausgefiltert.

full <- full %>% filter(age >= 25)
full

Als nächstes werden alle Zeilen mit Kreditkartenkäufern von den Nicht-Käufern getrennt

has_card_function <- function(x) {
  if (is.na(x)) {
    return(FALSE)
  } else {
    return(TRUE)
  }
}

# Erstelle die neue Spalte "has_card" mit der apply()-Funktion und der oben definierten Funktion
full$has_card <- sapply(full[, "card_id"], has_card_function)
full <- full %>% select(-card_id, -type.cards)

card_buyers <- full %>% filter(has_card == TRUE)

non_buyers <- full %>% filter(has_card == FALSE)

Jetzt können wir noch einige Variabeln entfernen, welche keinen Einfluss auf das Modell haben sollten.

Aufsummieren der Transaktionen

sample_n(transactions, 5)

Bei den Transaktionen ist jeweils die neue Balance und der Betrag der Transaktion angegebn. Das Problem dabei ist, dass alle Beträge positiv sind, auch wenn sie eigentlich abgezogen werden.

df <- transactions

# Konvertieren Sie das 'date'-Feld in ein Datum
df$date <- as.Date(df$date)

# Sortieren Sie das Dataframe nach Nutzer und Datum
df <- df[order(df$account_id, df$date), ]

# Gruppieren Sie das Dataframe nach Nutzer
df <- group_by(df, account_id)

# Iterieren Sie über jeden Nutzer und bearbeiten Sie die Transaktionen
df <- df %>% 
  summarize(transactions = {
    # Fügen Sie eine Spalte mit dem vorherigen Kontostand hinzu
    prev_balance <- ifelse(row_number() == 1, NA, lag(balance, order_by = date))

    # Berechnen Sie den Unterschied zwischen dem vorherigen Kontostand und dem aktuellen Kontostand
    difference <- balance - prev_balance

    # Fügen Sie eine Spalte mit der Transaktionsart hinzu
    type <- "add"
    type[difference < 0] <- "subtract"

    # Erstellen Sie das Dataframe mit den Transaktionen für jeden Nutzer
    transactions_df <- data.frame(amount, date, balance, prev_balance, difference, type)
    transactions_df
  }) %>%
  ungroup()

transactions <- unnest(df, transactions)

# Hinzufügen des ersten amounts bei jedem Account
transactions$difference <- ifelse(is.na(transactions$difference) & is.na(transactions$prev_balance) & (transactions$amount == transactions$balance), transactions$amount, transactions$difference)

transactions$amount <- NULL

transactions

Zusammenfassen der Transaktionen für Card Buyers

Um die Transaktions-Daten in unseren Modellen brauchen zu können, muss für jeden Kunde ein Rollup-Fenster erstellt werden. Dies fasst die Transaktionen der zwölf Monate vor dem Erhalt einer Kreditkarte zusammen (minus einen Monat Input Lag). Auf diesen Monaten werden die Transaktionen zusammengefasst.

Als erstes werden die Transaktionen von Kunden herausgefiltert, welche eine Kreditkarte haben.

account_ids <- card_buyers$account_id
buyer_transactions <- transactions[transactions$account_id %in% account_ids,]

Das issued-Datum soll zu den Transaktionen hinzugefügt werden, damit diese für jeden Kunden einzeln gefiltert werden können.

buyer_transactions <- merge(buyer_transactions, full[, c("account_id", "issued")], by="account_id")

Nun sollen Transaktionen so gefiltert werden, dass nur noch Transaktionen zwischen 13 Monaten und 1 Monat vor dem Issued Datum vorkommen.

filtered_df <- buyer_transactions %>%
  filter(date >= as.Date(paste0(format(issued - months(13), "%Y-%m"), "-01")) &
         date <= as.Date(paste0(format(issued - months(1), "%Y-%m"), "-01")) - 1)

Auf diesen Daten wird eine Gruppierung anhand der account_id und des Monats gemacht werden. Die Werte in difference und balance werden zu verschiedenen Metriken zusammengefasst: Auf beiden Werten erfassen wir das Minimum, das Maximum, den Durchschnitt, den Median und die Standardabweichung. Bei der balance erfassen wir die erste und die letzte Balance des Monats und bei difference die Anzahl positive und negative differences.

summary_df <- filtered_df %>%
  group_by(account_id, month = format(date, "%Y-%m")) %>%
  summarise(
    max_difference = max(difference),
    min_difference = min(difference),
    max_balance = max(balance),
    min_balance = min(balance),
    initial_balance = first(balance),
    end_balance = last(balance),
    mean_balance = mean(balance),
    median_balance = median(balance),
    std_balance = sd(balance),
    mean_difference = mean(difference),
    median_difference = median(difference),
    std_difference = sd(difference),
    count_positive_difference = sum(difference > 0),
    count_negative_difference = sum(difference < 0)
  )
summary_df <- summary_df %>%
  arrange(account_id)
summary_df

Jetzt haben wir für jede account_id eine Übersicht über die 12 Monate vor dem Kartenerhalt. Da es aber sein könnte, dass es Kunden gibt, welche nicht jeden Monat eine Transaktion hatten oder die Kreditkarte bereits im ersten Jahr erhalten haben, kontrollieren wir dies noch.

# Kontrolle, ob für jeden account_id 12 monate vorhanden sind
month_counts <- summary_df %>%
  group_by(account_id) %>%
  summarise(month_count = n_distinct(month))

# Prüfe, ob jedes account_id 12 Monate hat
month_counts <- month_counts %>% filter(month_count != 12)
month_counts
NA

162 Kunden haben also keine 12 kontinuierlichen Monate mit Transaktionen, bevor sie eine Karte bekommen. Wir filtern diese Kunden raus.

summary_df <- subset(summary_df, !account_id %in% month_counts$account_id)

Als nächstes nummerieren wir die Monate pro account_id von 1 bis 12 durch, um danach weiter damit arbeiten zu können.

# Sortieren nach account_id und Monat
summary_df <- summary_df[order(summary_df$account_id, rev(summary_df$month)),]

# Hinzufügen der Monatsnummer
summary_df$group_id <- ave(seq_along(summary_df$account_id), summary_df$account_id, FUN = function(x) {x})
summary_df$month_number <- 12

for (i in 2:nrow(summary_df)) {
  if (summary_df$account_id[i] != summary_df$account_id[i-1]) {
    summary_df$month_number[i] <- 12
  } else {
    summary_df$month_number[i] <- summary_df$month_number[i-1] - 1
  }
}

# Entferne die Spalte group_id
summary_df$group_id <- NULL
summary_df$month <- NULL

Nun möchten wir alle Informationen pro account_id auf einer Zeile haben. Dafür brauchen wir pivot_wider. So haben wir jede Kennzahl zwölf mal als Kolonne, jedes Mal mit der vorher erstellten Monatsnummer als Suffix.

summary_df_buyers <- summary_df %>%
  group_by(account_id) %>%
  pivot_wider(names_from = month_number,
              values_from = c(max_difference, min_difference, max_balance, min_balance, initial_balance, end_balance, mean_balance, median_balance, std_balance, median_balance, std_balance, mean_difference, median_difference, std_difference, count_positive_difference, count_negative_difference))

summary_df_buyers <- merge(summary_df_buyers, card_buyers, by = "account_id")
summary_df_buyers

Finden von ähnlichen Nutzern

Zu jedem Kartenkäufer soll nun ein ähnlicher Nichtkäufer gefunden werden

# Erstelle ein leeres DataFrame "similar_non_buyers"
similar_non_buyers <- data.frame()

# Iteriere über jeden Kunden im DataFrame "buyers"
for (i in 1:nrow(card_buyers)) {
  # Wähle den aktuellen Kunden aus dem DataFrame "buyers"
  current_buyer <- card_buyers[i, ]
  
  # Wähle die Kunden aus dem DataFrame "non_buyers" aus, die das gleiche Geschlecht haben und möglichst gleich alt sind und möglichst in der gleichen Region wohnen
  similar_non_buyers_temp <- non_buyers %>%
    filter(gender == current_buyer$gender,
           abs(age - current_buyer$age) <= 5,
           region == current_buyer$region)
  
  # Wähle den am besten passenden Kunden aus "similar_non_buyers_temp" aus
  best_match_index <- which.min(abs(similar_non_buyers_temp$age - current_buyer$age))
  best_match <- similar_non_buyers_temp[best_match_index, ]
  best_match$issued <- current_buyer$issued
  
  # damit nicht der gleiche non_buyer doppelt verwendet wird
  non_buyers <- non_buyers %>% filter(client_id != best_match$client_id)
  
  
  similar_non_buyers <- rbind(similar_non_buyers, best_match)
  
}

Zusammenfassen der Transaktionen für non buyers

Auch hier sollen die Transaktionen gleich wie bei den Käufern zusammengefasst werden.

account_ids <- similar_non_buyers$account_id
non_buyer_transactions <- transactions[transactions$account_id %in% account_ids,]

non_buyer_transactions <- merge(non_buyer_transactions, similar_non_buyers[, c("account_id", "issued")], by="account_id")
filtered_df <- non_buyer_transactions %>%
  filter(date >= as.Date(paste0(format(issued - months(13), "%Y-%m"), "-01")) &
         date <= as.Date(paste0(format(issued - months(1), "%Y-%m"), "-01")) - 1)
summary_df <- filtered_df %>%
  group_by(account_id, month = format(date, "%Y-%m")) %>%
  summarise(
    max_difference = max(difference),
    min_difference = min(difference),
    max_balance = max(balance),
    min_balance = min(balance),
    initial_balance = first(balance),
    end_balance = last(balance),
    mean_balance = mean(balance),
    median_balance = median(balance),
    std_balance = sd(balance),
    mean_difference = mean(difference),
    median_difference = median(difference),
    std_difference = sd(difference),
    count_positive_difference = sum(difference > 0),
    count_negative_difference = sum(difference < 0)
  )
summary_df <- summary_df %>%
  arrange(account_id)
# Kontrolle, ob für jeden account_id 12 monate vorhanden sind
month_counts <- summary_df %>%
  group_by(account_id) %>%
  summarise(month_count = n_distinct(month))

# Prüfe, ob jedes account_id 12 Monate hat
month_counts <- month_counts %>% filter(month_count != 12)
month_counts
NA

Auch hier haben wieder einige Kunden weniger als 12 kontinuierliche Monate.

summary_df <- subset(summary_df, !account_id %in% month_counts$account_id)
summary_df <- summary_df[order(summary_df$account_id, rev(summary_df$month)),]
summary_df$group_id <- ave(seq_along(summary_df$account_id), summary_df$account_id, FUN = function(x) {x})

summary_df$month_number <- 12

for (i in 2:nrow(summary_df)) {
  if (summary_df$account_id[i] != summary_df$account_id[i-1]) {
    summary_df$month_number[i] <- 12
  } else {
    summary_df$month_number[i] <- summary_df$month_number[i-1] - 1
  }
}

# Entferne die Spalte group_id
summary_df$group_id <- NULL
summary_df$month <- NULL
summary_df_non_buyers <- summary_df %>%
  group_by(account_id) %>%
  pivot_wider(names_from = month_number,
              values_from = c(max_difference, min_difference, max_balance, min_balance, initial_balance, end_balance, mean_balance, median_balance, std_balance, median_balance, std_balance, mean_difference, median_difference, std_difference, count_positive_difference, count_negative_difference))

Die Transaktionsdaten werden mit den anderen Daten zusammengefügt, um pro Kunde eine Zeile in einem Dataframe zu haben.


summary_df_non_buyers <- merge(summary_df_non_buyers, similar_non_buyers, by = "account_id")
merge(summary_df_non_buyers, non_buyers, by = "account_id")
final_df <- rbind(summary_df_buyers, summary_df_non_buyers)

Jetzt muss noch dass issued-Datum sowie weitere Variabeln entfernt werden.

# Entferne weitere unnötige Variabeln wie ID's oder Werte, welche überall gleich sind
final_df <- final_df %>% select(-client_id, -district_id, -district_id.accounts, -disp_id, -type, -loan_id, -account_id, -issued)

Ausserdem scheinen einige Variabeln als Faktoren im Datensatz zu sein, welche eigentlich numerisch wären.

final_df$unemployment_rate_95 <- as.numeric(final_df$unemployment_rate_95)
Warning: NAs introduced by coercion
final_df$unemployment_rate_95.accounts <- as.numeric(final_df$unemployment_rate_95.accounts)
Warning: NAs introduced by coercion
final_df$crimes_95 <- as.numeric(final_df$crimes_95)
Warning: NAs introduced by coercion
final_df$crimes_95.accounts <- as.numeric(final_df$crimes_95.accounts)
Warning: NAs introduced by coercion

Modelle

Als nächstes sollen Modelle trainiert und evaluiert werden. Um die Resultate zu reproduzieren, wird hier ein initialier seed gesetzt.

set.seed(27)

Funktionen zur Evaluierung von Modellen

Train-Test-Split

Als Vorbereitung für die Modelle müssen wir unsere Daten zu Trainings- und Testdaten unterteilen. Dafür erstellen wir eine Funktion, welche auf verschiedenen Varianten des Datensatz gebraucht werden kann.

split_data <- function(df, test_size = 0.2) {
  split <- createDataPartition(df$has_card, p = 1 - test_size, list = FALSE)
  train <- df[split, ]
  test <- df[-split, ]
  
  return(list(train = train, test = test))
}

Metriken

Damit wir die verschiedenen Modelle besser evaluieren können, müssen wir die gleichen Kennzahlen und Auswertungen pro Modell machen. Zu diesem Zweck definieren wir einige Funktionen, damit wir weniger redundanten Code haben und unsere Ergebnisse in einem einheitliche, vergleichbaren Format daherkommen.

Dafür erstellen wir eine Funktion, die die Genauigkeit (Accuracy), Cohen’s Kappa, Matthews Korrelation, Präzision (Precision), Erinnerung (Recall) und den F1-Score für eine Reihe von Vorhersagen und deren entsprechenden wahren Werte berechnet.

Was bedeuten diese Kennzahlen genau?

Accuracy (Genauigkeit): Die Accuracy ist der Prozentsatz der Vorhersagen, die mit den tatsächlichen Werten übereinstimmen. Sie wird berechnet als Anzahl der korrekten Vorhersagen geteilt durch die Gesamtzahl der Vorhersagen.

Cohen’s Kappa (Cohen’s Kappa): Cohen’s Kappa ist eine Messgröße für die Qualität von binären Klassifikationen. Es wird verwendet, um die Übereinstimmung zwischen zwei Klassifikatoren zu messen, indem es die Übereinstimmung über der erwarteten Übereinstimmung durch Zufall berechnet. Ein Kappa-Wert von 1 bedeutet perfekte Übereinstimmung, ein Wert von 0 bedeutet keine Übereinstimmung, die besser ist als Zufall, und ein Wert von -1 bedeutet komplett falsche Klassifikationen.

Matthews correlation coefficient (Matthews Korrelation): Der Matthews Korrelation Coefficient (MCC) ist eine Messgröße für die Qualität von binären Klassifikationen. Er reicht von -1 bis 1, wobei ein Wert von 1 perfekte Klassifikation bedeutet, ein Wert von 0 eine Klassifikation, die nicht besser als Zufall ist, und ein Wert von -1 eine komplett falsche Klassifikation bedeutet.

Precision (Präzision): Die Präzision ist der Prozentsatz der Vorhersagen, die tatsächlich korrekt waren, unter der Annahme, dass alle Vorhersagen korrekt sind. Sie wird berechnet als Anzahl der korrekten Vorhersagen für die positive Klasse geteilt durch die Gesamtzahl der Vorhersagen für die positive Klasse.

Recall (Erinnerung): Der Recall ist der Prozentsatz der tatsächlich positiven Werte, die korrekt vorhergesagt wurden. Er wird berechnet als Anzahl der korrekten Vorhersagen für die positive Klasse geteilt durch die Gesamtzahl der tatsächlich positiven Werte.

F1 score (F1-Wert): Der F1-Wert ist ein Maß für die Qualität von binären Klassifikationen, das die Harmoniesche Mischung von Präzision und Recall darstellt. Es wird berechnet als der Harmoniesche Mittelwert von Präzision und Recall. Ein hoher F1-Wert bedeutet, dass sowohl Präzision als auch Recall hoch sind.

get_metrics <- function(predictions, true_values) {
  # Calculate accuracy
  accuracy <- sum(predictions == true_values) / length(predictions)
  
  # Calculate Cohen's kappa
  n <- length(predictions)
observed_agreement <- sum(predictions == true_values)
expected_agreement <- sum(predictions == true_values) / n
kappa <- (observed_agreement - expected_agreement) / (n - expected_agreement)
  
  # Calculate Matthews correlation coefficient
  confusion_matrix <- table(predictions, true_values)
  tp <- confusion_matrix[2,2]
  tn <- confusion_matrix[1,1]
  fp <- confusion_matrix[2,1]
  fn <- confusion_matrix[1,2]
  matthews <- (tp * tn - fp * fn) / sqrt((tp + fp) * (tp + fn) * (tn + fp) * (tn + fn))
  
  # Calculate precision and recall
  precision <- confusion_matrix[2,2] / sum(confusion_matrix[2,])
  recall <- confusion_matrix[2,2] / sum(confusion_matrix[,2])
  
  # Calculate F1 score
  f1 <- 2 * (precision * recall) / (precision + recall)
  
  # Create a data frame of the metrics
  metrics <- data.frame(accuracy = accuracy, kappa = kappa, matthews = matthews,
                        precision = precision, recall = recall, f1 = f1)
  
  # Return the data frame
  return(metrics)
}

Konfusionsmatrix mit Plot

Diese Funktion erstellt eine Konfusionsmatrix und gibt sie als Plot zurück.

Eine Konfusionsmatrix ist ein wichtiges Werkzeug zur Evaluation von Klassifikationsmodellen. Sie zeigt an, wie gut das Modell in der Lage ist, die verschiedenen Klassen richtig zu identifizieren. In einer Konfusionsmatrix werden die tatsächlichen und die von dem Modell vorhergesagten Klassen gegenübergestellt. Die Matrix ist in vier Quadranten unterteilt: true positives (TP), true negatives (TN), false positives (FP) und false negatives (FN). TP sind die Fälle, in denen das Modell die Klasse richtig vorhergesagt hat, TN sind die Fälle, in denen das Modell die Klasse richtig vorhergesagt hat und diese Klasse auch tatsächlich vorliegt, FP sind die Fälle, in denen das Modell eine Klasse vorhergesagt hat, die in Wirklichkeit nicht vorliegt, und FN sind die Fälle, in denen das Modell eine Klasse nicht vorhergesagt hat, die in Wirklichkeit vorliegt. Eine Konfusionsmatrix ist hilfreich, um die Genauigkeit, Sensitivität und Spezifität des Modells zu berechnen und um zu sehen, an welchen Stellen das Modell Schwächen hat. Sie kann auch verwendet werden, um die Leistung von verschiedenen Modellen miteinander zu vergleichen.

plot_confusion_matrix <- function(predictions, true_values) {
  # Erstelle eine Confusion Matrix als Data Frame
  confusion_matrix_df <- data.frame(predictions, true_values)
  
  # Zähle die Häufigkeiten jeder Kombination von Vorhersage- und True-Werten
  counts_df <- count(confusion_matrix_df, predictions, true_values)
  
  # Erstelle einen ggplot-Plot
  ggplot(data = counts_df, aes(x = predictions, y = true_values)) +
    geom_tile(aes(fill = n)) +
    geom_text(aes(label = n)) +
    scale_fill_gradient(low = "white", high = "darkgreen") +
    labs(x = "Predicted Class", y = "True Class", title = "Confusion Matrix")
}

ROC / AUC

Diese Funktion zeichnet die ROC-Kurve und berechnet die Area und Curve.

Die ROC-Kurve (Receiver Operating Characteristic curve) ist ein wichtiges Werkzeug zur Bewertung von Klassifikatoren. Sie zeigt die Leistung des Klassifikators bei verschiedenen Schwellenwerten an, die zur Unterscheidung zwischen zwei Klassen verwendet werden. Die ROC-Kurve ist besonders nützlich, wenn die beiden Klassen im Verhältnis unausgeglichen sind, wie es oft der Fall ist, wenn es darum geht, seltene Ereignisse wie Krankheiten oder Betrug zu erkennen.

Die ROC-Kurve ist auf der x-Achse der falsch-positiv-Rate (FPR) und auf der y-Achse der wahr-positiv-Rate (TPR) aufgetragen. Der FPR gibt an, wie viele falsch positive Ergebnisse es gibt, während der TPR angibt, wie viele wahr positive Ergebnisse erzielt werden. Ein perfekter Klassifikator würde eine ROC-Kurve haben, die im oberen linken Bereich beginnt und nach rechts oben verläuft, wobei alle Fälle korrekt klassifiziert werden. Ein zufälliger Klassifikator würde eine diagonal verlaufende ROC-Kurve haben, da die FPR und TPR zufällig verteilt sind.

Die AuC (Area Under the Curve) ist eine Metrik, die aus der ROC-Kurve berechnet wird und die Leistung des Klassifikators zusammenfasst. Sie gibt an, wie gut der Klassifikator im Vergleich zu einem zufälligen Klassifikator ist. Eine AUC von 1 bedeutet, dass das Modell perfekt in der Lage ist, positive und negative Klassen zu unterscheiden, während eine AUC von 0.5 bedeutet, dass das Modell keine bessere Leistung als Zufall erzielt. Die AUC kann Werte zwischen 0 und 1 annehmen. Eine AUC von 0 bedeutet, dass das Modell völlig inkorrekt ist. Im Allgemeinen gilt, je größer die AUC, desto besser ist das Modell im Vergleich zu anderen Modellen.

make_roc_plot_and_get_auc <- function(predictions, true_values){

roc_curve <- roc(as.numeric(predictions), as.numeric(true_values) - 1)

plot(roc_curve, xlab = "False Positive Rate", ylab = "True Positive Rate", main ="ROC Curve")

auc <- auc(roc_curve)

return(auc)
}

Feature Importance

Um die Feature Importance genauer zu untersuchen, definieren wir eine Funktion, welche für ein Modell die Feature Importance berechnet und die Top 10 Features in einem Barplot ausgibt.Die Funktion plot_feature_importance() nimmt als Eingabeparameter ein maschinelles Lernmodell und plottet die Feature-Importance des Modells als Barplot. Die Funktion unterscheidet drei Arten von Modellen: Logistische Regressionen, Entscheidungsbäume und Random Forest. Für jede Art von Modell wird die Feature-Importance auf eine spezifische Art und Weise berechnet. Anschließend werden die Feature-Importance-Werte und die Namen der Features absteigend sortiert, wobei nur die 10 wichtigsten Features berücksichtigt werden. Der Barplot zeigt dann die Feature-Importance-Werte für die entsprechenden Features an. Der Titel des Plots lautet “Feature Importance” und die y-Achse ist mit “Importance” beschriftet. Die Namen der Features werden rotiert angezeigt, um Platz zu sparen, und die Farben der einzelnen Features sind unterschiedlich.

plot_feature_importance <- function(model) {
  
  # Logistische Regression
  if (class(model)[1] == "glm") {
    importance <- abs(coef(model)[-1])
    names <- names(importance)
    
    # Sort feature importance and names in decreasing order
  importance <- head(importance[order(importance, decreasing = TRUE)], 10)
  names <- names[order(importance, decreasing = TRUE)]
  } 
  
  # Decision Tree
  else if (class(model)[1] == "rpart") {
     # Extract feature importance from model
  importance <- model$importance[1,]
  names <- row.names(model$importance)
  
  # Sort feature importance and names in decreasing order
  importance <- importance[order(importance, decreasing = TRUE)]
  names <- names[order(importance, decreasing = TRUE)]
    
  } 
  
  # Random Forest
  else if (class(model)[1] == "randomForest") {
    importance <- model$importance
    names <- row.names(importance)
  } 
  
  # Unrecognized model
  else {
    stop("Unrecognized model")
  }
  
  
  
  # Plot feature importance
  barplot(importance, names.arg = names, las = 2, cex.names = 0.6,
          main = "Feature Importance", xlab = "", ylab = "Importance")
}

plot_feature_importance(model)
Error in order(importance, decreasing = TRUE) : 
  argument 1 is not a vector

Threshold Plot


find_best_threshold <- function(predictions, actual_values) {
best_threshold <- 0
best_f1 <- 0
thresholds <- seq(0.01, 0.99, 0.01)
f1_scores <- rep(0, length(thresholds))

for (i in 1:length(thresholds)) {
  # Set the threshold for the predictions
  predictions_threshold <- ifelse(predictions > thresholds[i], TRUE, FALSE)
  
  if (all(predictions_threshold)) {
  next
} else if (all(!predictions_threshold)) {
  next
}
  
  # Calculate the f1
  f1 <- get_metrics(predictions_threshold, actual_values)$f1
  
  f1_scores[i] <- f1
  
  # Update the best threshold and best f1 if necessary
  if (f1 > best_f1) {
    best_threshold <- thresholds[i]
    best_f1 <- f1
  }
}

# Display the best threshold and best recall
print(paste("Best threshold:", best_threshold))
print(paste("Best F1:", best_f1))

plot(thresholds, f1_scores, type = "l", xlab = "Threshold", ylab = "F1 Score", main = "F1 Score with different thresholds")
}

Baseline Modell

Als erstes soll eine logistische Regression mit den Informationen Alter, Geschlecht, Domizilregion, Vermögen (balance-Schnitt über alle Monate) und Umsatz (difference-Schnitt über alle Monate) als Baseline Modell erstellt werden.Dafür müssen wir kurz ein neues Dataframe erstellen.

baseline_data <- final_df
baseline_data$mean_balance <- rowMeans(final_df[, c("mean_balance_1", "mean_balance_2", "mean_balance_3", "mean_balance_4", 
                                                "mean_balance_5", "mean_balance_6", "mean_balance_7", "mean_balance_8", 
                                                "mean_balance_9", "mean_balance_10", "mean_balance_11", "mean_balance_12")])
baseline_data$mean_difference <- rowMeans(final_df[, c("mean_difference_1", "mean_difference_2", "mean_difference_3", "mean_difference_4", 
                                                  "mean_difference_5", "mean_difference_6", "mean_difference_7", "mean_difference_8", 
                                                  "mean_difference_9", "mean_difference_10", "mean_difference_11", "mean_difference_12")])
baseline_data <- baseline_data[, c("age", "gender", "region", "has_card", "mean_balance", "mean_difference")]
baseline_data$has_card <- as.factor(baseline_data$has_card)

Darauf wird ein Train-Test-Split mit 80% Trainingsdaten und 20% Testdaten gebraucht.

splits <- split_data(baseline_data, test_size = 0.2)
train <- splits$train
test <- splits$test

Das Regressionsmodell wird auf den Trainingsdaten trainiert.

# Fit the model on the training data
model <- glm(has_card ~ ., data = train, family = binomial)

Als nächstes sollen die Predictions gemacht und damit die Metriken erstellt werden. Da die logistische Regression eine Zahl zwischen 0 und 1 zurückgibt, müssen wir anhand eines Thresholds die Werte zu TRUE und FALSE umwandeln. Der Threshold gibt an, ab welchem Wahrscheinlichkeitswert eine Vorhersage als positiv betrachtet wird. Standardmäßig ist der Threshold auf 0.5 gesetzt, was bedeutet, dass alle Wahrscheinlichkeiten größer als 0.5 als positiv und alle Wahrscheinlichkeiten kleiner als 0.5 als negativ betrachtet werden.

# Make predictions on the test data
predictions <- predict(model, test, type = "response")

threshold <- 0.5
predictions_threshold <- ifelse(predictions > threshold, TRUE, FALSE)


get_metrics(predictions_threshold, test$has_card)
plot_confusion_matrix(predictions_threshold, test$has_card)

make_roc_plot_and_get_auc(predictions_threshold, test$has_card)
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Area under the curve: 0.7489

plot_feature_importance(model)

Threshold

Der Threshold-Wert könnte aber angepasst werden. Je nach Veränderung werden die verschiedenen Metriken besser oder schlechter. Die Frage dabei ist, ob wir eher mehr falsche Positivvorhersagen oder Negativvorhersagen wollen. Wenn wir den Threshold senken, wird bspw. der Recall höher. Wenn wir den Threshold erhöhen, wird die Precision höher. Es ist aber nicht sinnvoll, anhand des Thresholds diese beiden Metriken zu maximieren, da es bei beiden fast keine Positiven bzw. Negative Predictions mehr gibt.

Der F1 ist eine Zusammenfassung der Precision und der Recall. Wir untersuchen, mit welchem Threshold der höchste F1-Score erzielt werden kann.

find_best_threshold(predictions, test$has_card)
[1] "Best threshold: 0.4"
[1] "Best F1: 0.861924686192469"

Wie sehen die anderen Metriken mit diesem Threshold aus?

predictions_threshold <- ifelse(predictions > best_threshold, TRUE, FALSE)
get_metrics(predictions_threshold, test$has_card)

Regressionsmodell mit allen Daten

Nun soll dieses Baseline-Modell verbessert werden. Als erstes probieren wir, das gleiche Modell (Logistische Regression) mit mehr Input-Parametern zu trainieren.

Da die logistische Regression Probleme mit Faktoren hat, welche nur im Trainings- bzw. Testdatensatz vorkommen und auch nicht gut mit NA’s umgehen kann, müssen wir zuerst noch einige Anpassungen am Datensatz vornehmen.

Als erstes entfernen wir alle Kolonnen, welche Faktoren sind und mehr als 10 verschiedene Ausprägungen haben.

# Ermittle die numerischen Merkmale in den Trainingsdaten
numeric_vars <- sapply(final_df, is.numeric)

# Erstelle ein Subset der Trainingsdaten ohne die numerischen Merkmale
train_no_numeric <- final_df[, !numeric_vars]

# Ermittle die Anzahl der Kategorien für jedes Merkmal
num_categories <- sapply(train_no_numeric, function(x) length(unique(x)))

# Überprüfe, ob ein Merkmal zu viele Kategorien hat
too_many_categories <- num_categories > 10

# Gib die Namen der Merkmale aus, die zu viele Kategorien haben
columns_to_remove <- colnames(train_no_numeric)[too_many_categories]


# Ermittle die Spaltennamen, die behalten werden sollen
keep_columns <- setdiff(colnames(final_df), columns_to_remove)

# Erstelle ein Subset des Dataframes mit den behaltenen Spaltennamen
final_df_simplified <- final_df[, keep_columns]

columns_to_remove
[1] "birth_date"             "date"                   "date.loans"             "district_name"          "district_name.accounts"

Nun muss der Datensatz noch auf NA’s überprüft werden.

# Count the number of NA values in the data frame
num_na <- sum(is.na(final_df_simplified))

# Print the total number of NA values
print(paste("Total number of NA values:", num_na))
[1] "Total number of NA values: 0"
# Create a logical vector indicating whether each element is NA
na_matrix <- is.na(final_df_simplified)

# Sum the number of NA values per row
na_counts <- rowSums(na_matrix)

# Count the rows with NA values
num_na_rows <- sum(na_counts > 0)

# Print the number of rows with NA values
print(paste("Number of rows with NA values:", num_na_rows))
[1] "Number of rows with NA values: 0"
# Sum the number of NA values per column
na_counts_cols <- colSums((na_matrix))

# Count the columns with NA values
num_na_cols <- sum(na_counts_cols > 0)

# Print the number of columns with NA values
print(paste("Number of columns with NA values:", num_na_cols))
[1] "Number of columns with NA values: 0"

Fast jede Zeile hat irgendwo ein NA. Es sind auch viele Kolonnen betroffen. Wir können also nicht alle Observationen oder Variabeln mit NA’s entfernen, da sonst der Datenverlust sehr gross wäre. Daher imputieren wir die numerischen fehlenden Werte mit dem Median und die fehlenden kategorialen Werte mit dem Wert, welcher am meisten vorkommt.

# Impute NA numbers with the median
final_df_simplified <- final_df_simplified %>% 
  mutate_if(is.numeric, list(~ if_else(is.na(.), median(., na.rm = TRUE), .)))

# Impute NA strings/factors with the most common value
final_df_simplified <- final_df_simplified %>% 
  mutate_if(is.character, list(~ if_else(is.na(.), mode(.), .)))
sum(is.na(final_df_simplified))
[1] 0
splits <- split_data(final_df_simplified, test_size = 0.2)
train <- splits$train
test <- splits$test
# Fit the model on the training data
model <- glm(has_card ~ ., data = train, family = binomial)
# Make predictions on the test data
predictions <- predict(model, test, type = "response")

threshold <- 0.5
predictions_threshold <- ifelse(predictions > threshold, TRUE, FALSE)

get_metrics(predictions_threshold, test$has_card)

make_roc_plot_and_get_auc(predictions_threshold, test$has_card)
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Area under the curve: 0.8689

plot_feature_importance(model)

find_best_threshold(predictions, test$has_card)
[1] "Best threshold: 0.4"
[1] "Best F1: 0.861924686192469"

Mit nur den beiden wichtigsten Variabeln

final_df_simplified_two_variables <- final_df_simplified %>% select(max_difference_12, max_difference_11)
model <- glm(has_card ~ ., data = train, family = binomial)
predictions <- predict(model, test, type = "response")

threshold <- 0.5
predictions_threshold <- ifelse(predictions > threshold, TRUE, FALSE)

get_metrics(predictions_threshold, test$has_card)

Decision Tree


model <- rpart(has_card ~ ., data = train, method = "class")

# Make predictions on the test data (second column is for probability of true)
predictions <- predict(model, test, type = "prob")[, 2]

threshold <- 0.5
predictions_threshold <- ifelse(predictions > threshold, TRUE, FALSE)

get_metrics(predictions_threshold, test$has_card)
plot_confusion_matrix(predictions_threshold, test$has_card)

make_roc_plot_and_get_auc(predictions_threshold, test$has_card)
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Area under the curve: 0.8262

find_best_threshold(predictions, test$has_card)
[1] "Best threshold: 0.24"
[1] "Best F1: 0.838983050847458"

rpart.plot(model)

Die Klassifikation ist um einiges besser auf dem Decision Tree. Es scheint also für unseren Verwendungszweck der bessere Algorithmus zu sein. Wir untersuchen noch Erweiterungen des Decision Trees: der Random Forest.

Random Forest

Beim Random Forest muss unsere Zielvariabel noch in einen Faktor umgewandelt werden, damit die Wahrscheinlichkeiten vorhergesagt werden können.

train$has_card <- as.factor(train$has_card)
# Fit a random forest model to the training data
model <- randomForest(has_card ~ ., data = train, method = "class")

# Make predictions on the test data (second column is for probability of true)
predictions <- predict(model, test, type = "prob")[, 2]

# Threshold the predicted probabilities
threshold <- 0.5
predictions_threshold <- ifelse(predictions > threshold, TRUE, FALSE)

# Evaluate the model's performance
get_metrics(predictions_threshold, test$has_card)

Der Random Forest ist ein wenig besser als der Decision Tree. Als nächstes wollen wir probieren, ob eine Hyperparameteroptimierung unser Resultat noch verbessern kann.

plot_confusion_matrix(predictions_threshold, test$has_card)

make_roc_plot_and_get_auc(predictions_threshold, test$has_card)
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Area under the curve: 0.8662

find_best_threshold(predictions, test$has_card)
[1] "Best threshold: 0.5"
[1] "Best F1: 0.869565217391304"

importance_values <- varImp(model)
importance_values$variable <- rownames(varImp(model)) 
importance_values <- importance_values[order(importance_values$Overall, decreasing = TRUE),]

# Wählen Sie die top 10 Merkmale aus
top_features <- head(importance_values, n = 10)
top_features
NA
# Initialize empty vector to store f1-scores
f1_scores <- c()

# Loop through each variable and fit a random forest model
for (i in 1:50) {
  # Select subset of variables
  vars_subset <- importance_values$variable[1:i]
  
  # Fit random forest model
  train_reduced <- train %>% select(has_card, vars_subset)
  model <- randomForest(has_card ~ ., data = train_reduced, method = "class")
  
  # Make predictions on test data
  predictions <- predict(model, test, type = "prob")[, 2]
  
  # Threshold predictions
  threshold <- 0.5
  predictions_threshold <- ifelse(predictions > threshold, TRUE, FALSE)
  
  # Calculate f1-score
  f1 <- get_metrics(predictions_threshold, test$has_card)$f1
  
  # Append f1-score to vector
  f1_scores <- c(f1_scores, f1)
}

# Find the index of the maximum f1-score
best_index <- which.max(f1_scores)

# Print the best f1-score and the corresponding number of variables
print(paste("Best f1-score:", f1_scores[best_index]))
[1] "Best f1-score: 0.871794871794872"
print(paste("Number of variables:", best_index))
[1] "Number of variables: 7"
# Plot the f1-scores
plot(1:length(f1_scores), f1_scores, type = "l", xlab = "Number of variables", ylab = "f1-score")

set.seed(27)
# Fit a random forest model to the training data
model <- randomForest(has_card ~ ., data = train %>% select(has_card, importance_values$variable[1:7]), method = "class")

# Make predictions on the test data (second column is for probability of true)
predictions <- predict(model, test, type = "prob")[, 2]

# Threshold the predicted probabilities
threshold <- 0.5
predictions_threshold <- ifelse(predictions > threshold, TRUE, FALSE)

# Evaluate the model's performance
get_metrics(predictions_threshold, test$has_card)

Hyperparamter Optimierung auf Random Forest

set.seed(27)
# Define the hyperparameter grid
param_grid <- expand.grid(mtry = c(0.1, 1, 10, 100),
                         splitrule = c("gini", "extratrees"),
                         min.node.size = c(1, 10, 15, 20),
                         max.depth = seq(1, 30, 5),
                         ntree = c(100, 500, 1000))

# Define the model using the train() function
model <- randomForest(has_card ~ .,
               data = train %>% select(has_card, importance_values$variable[1:7]),
               method = "rf",
               tuneGrid = param_grid,
               trControl = trainControl(method = "cv", number = 5))

# Make predictions on the test data
predictions <- predict(model, test, type = "prob")[, 2]

# Threshold the predicted probabilities
threshold <- 0.5
predictions_threshold <- ifelse(predictions > threshold, TRUE, FALSE)

print(model)

Call:
 randomForest(formula = has_card ~ ., data = train %>% select(has_card,      importance_values$variable[1:7]), method = "rf", tuneGrid = param_grid,      trControl = trainControl(method = "cv", number = 5)) 
               Type of random forest: classification
                     Number of trees: 500
No. of variables tried at each split: 2

        OOB estimate of  error rate: 16.79%
Confusion matrix:
      FALSE TRUE class.error
FALSE   282   98  0.25789474
TRUE     37  387  0.08726415
# Evaluate the model's performance
get_metrics(predictions_threshold, test$has_card)

Recursive Feature Elimnation

Eine Möglichkeit, die Leistung von Random Forest zu verbessern, ist die Verwendung von Recursive Feature Elimination (RFE).

RFE ist ein Feature Selection-Verfahren, das dazu verwendet wird, die wichtigsten Features (also diejenigen Merkmale, die für die Vorhersage am wichtigsten sind) auszuwählen und alle anderen zu entfernen. Dies hat mehrere Vorteile:

Es reduziert die Laufzeit von Random Forest, da weniger Features verarbeitet werden müssen. Es kann dazu beitragen, Overfitting zu vermeiden, indem es irrelevanten oder redundanten Features entfernt. Es kann dazu beitragen, die Interpretierbarkeit von Random Forest zu verbessern, da wichtigere Features leichter zu verstehen sind.

if (FALSE) {
# Define the control object
control <- rfeControl(functions = rfFuncs,
                      method = "repeatedcv",
                      repeats = 5)

# Perform RFE
model <- rfe(x = train %>% select(-has_card), y = train$has_card,
             sizes = c(1:19, seq(from = 20, to = ncol(train), by = 10)),
             rfeControl = control)

# Extract the selected features
selected_features <- model$optVariables

print(model)
}
# Fit a random forest model using the selected features
model <- randomForest(has_card ~ ., data = train[c(selected_features, "has_card")], method = "class")

# Make predictions on the test data
predictions <- predict(model, test, type = "prob")[, 2]

# Threshold the predicted probabilities
threshold <- 0.5
predictions_threshold <- ifelse(predictions > threshold, TRUE, FALSE)

# Evaluate the model's performance
get_metrics(predictions_threshold, test$has_card)
LS0tDQp0aXRsZTogImFtbCINCnN1YnRpdGxlOiAiTWluaS1DaGFsbGVuZ2UgMSINCmF1dGhvcjogIlBhc2NhbCBCZXJnZXIgdW5kIFJhcGhhZWwgU3RyZWJlbCINCmRhdGU6ICIxOC4gT2t0b2JlciAyMDIyIg0Kb3V0cHV0Og0KICBodG1sX25vdGVib29rOg0KICAgIHRvYzogdHJ1ZQ0KICAgIHRvY19kZXB0aDogNA0KICAgIGRmX3ByaW50OiBwYWdlZA0KICAgIHRvY19mbG9hdDoNCiAgICAgIGNvbGxhcHNlZDogdHJ1ZQ0KICAgICAgc21vb3RoX3Njcm9sbDogdHJ1ZQ0KICAgIHRoZW1lOiB1bml0ZWQNCiAgICBoaWdobGlnaHQ6IHRhbmdvDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQotLS0NClItVmVyc2lvbjogKipbRGVmYXVsdF0gWzY0LWJpdF0gQzpcXFByb2dyYW0gRmlsZXNcXFJcXFItNC4xLjAqKg0KDQojIEltcG9ydHMNCg0KYGBge3IgZWNobz1GQUxTRSwgY2FjaGU9RkFMU0UsIHJlc3VsdHM9RkFMU0UsIGNvbW1lbnQ9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQojIGNsZWFyIGVudmlyb25tZW50DQpybShsaXN0ID0gbHMoKSkNCg0KIyBuw7Z0aWdlIFBhY2tldGUNCnBhY2thZ2VzIDwtIGMoInRpZHl2ZXJzZSIsICJkYXRhLnRhYmxlIiwgInRpZHltb2RlbHMiLCAibHVicmlkYXRlIiwgImNhcmV0IiwgInBST0MiLCAicmFuZG9tRm9yZXN0IikNCg0KIyBOb2NoIG5pY2h0IGluc3RhbGxpZXJ0ZSBQYWtldGUgaW5zdGFsbGllcmVuDQppbnN0YWxsZWRfcGFja2FnZXMgPC0gcGFja2FnZXMgJWluJSByb3duYW1lcyhpbnN0YWxsZWQucGFja2FnZXMoKSkNCg0KaWYgKGFueShpbnN0YWxsZWRfcGFja2FnZXMgPT0gRkFMU0UpKSB7DQogIGluc3RhbGwucGFja2FnZXMocGFja2FnZXNbIWluc3RhbGxlZF9wYWNrYWdlc10pDQp9DQojIExhZGVuIGRlciBQYWNrZXRlDQppbnZpc2libGUobGFwcGx5KHBhY2thZ2VzLCBsaWJyYXJ5LCBjaGFyYWN0ZXIub25seSA9IFRSVUUpKQ0KDQojIGNoYW5nZSBvcHRpb25zDQpvcHRpb25zKGRwbHlyLnN1bW1hcmlzZS5pbmZvcm0gPSBGQUxTRSkNCmBgYA0KDQpJbiBmb2xnZW5kZW0gSnVuayB3ZXJkZW4gYWxsZSBUYWJlbGxlbiBhdXMgZGVuIENTVidzIGVpbmdlbGVzZW4uDQpEb2t1IERhdGVuOiBodHRwczovL3NvcnJ5LnZzZS5jei9+YmVya2EvY2hhbGxlbmdlL1BBU1QvaW5kZXguaHRtbA0KKHJlY2h0ZSBTZWl0ZSBQS0REJzk5IENoYWxsZW5nZSA+IERhdGEgPiBGaW5hbmNpYWwgRGF0YSBEZXNjcmlwdGlvbikNCg0KDQojIEVpbmxlc2VuIGRlciBEYXRlbg0KDQpEZXIgRGF0ZW5zYXR6IGJlc3RlaHQgYXVzIGFjaHQgdmVyc2NoaWVkZW5lbiBUYWJlbGxlbiwgd2VsY2hlIHRlaWxzIGR1cmNoIEtleXMgbWl0ZWluYW5kZXIgdmVya27DvHBmdCBzaW5kLg0KYGBge3J9DQpyb290X3BhdGggPC0gIi4veHNlbGxpbmdfYmFua2luZ19kYXRhLTEveHNlbGxpbmdfYmFua2luZ19kYXRhLyINCg0KYWNjb3VudHMgPC0gcmVhZC5jc3YocGFzdGUwKHJvb3RfcGF0aCwgImFjY291bnQuY3N2IiksIGhlYWRlciA9IFRSVUUsIHNlcCA9ICI7IikNCmNhcmRzIDwtIHJlYWQuY3N2KHBhc3RlMChyb290X3BhdGgsICJjYXJkLmNzdiIpLCBoZWFkZXIgPSBUUlVFLCBzZXAgPSAiOyIpDQpjbGllbnRzIDwtIHJlYWQuY3N2KHBhc3RlMChyb290X3BhdGgsICJjbGllbnQuY3N2IiksIGhlYWRlciA9IFRSVUUsIHNlcCA9ICI7IikNCmRpc3Bvc2l0aW9ucyA8LSByZWFkLmNzdihwYXN0ZTAocm9vdF9wYXRoLCAiZGlzcC5jc3YiKSwgaGVhZGVyID0gVFJVRSwgc2VwID0gIjsiKQ0KZGlzdHJpY3RzIDwtIHJlYWQuY3N2KHBhc3RlMChyb290X3BhdGgsICJkaXN0cmljdC5jc3YiKSwgc2VwID0gIjsiKQ0KbG9hbnMgPC0gcmVhZC5jc3YocGFzdGUwKHJvb3RfcGF0aCwgImxvYW4uY3N2IiksIGhlYWRlciA9IFRSVUUsIHNlcCA9ICI7IikNCm9yZGVycyA8LSByZWFkLmNzdihwYXN0ZTAocm9vdF9wYXRoLCAib3JkZXIuY3N2IiksIGhlYWRlciA9IFRSVUUsIHNlcCA9ICI7IikNCnRyYW5zYWN0aW9ucyA8LSByZWFkLmNzdihwYXN0ZTAocm9vdF9wYXRoLCAidHJhbnMuY3N2IiksIGhlYWRlciA9IFRSVUUsIHNlcCA9ICI7IikNCmBgYA0KDQojIENsZWFuaW5nDQoNCiMjIEFjY291bnRzDQpgYGB7cn0NCnNhbXBsZV9uKGFjY291bnRzLCA1KQ0KYGBgDQpEaWUgQWNjb3VudC1UYWJlbGxlIGVudGjDpGx0IHZpZXIgS29sb25uZW46IGRpZSBBY2NvdW50LUlELCBkaWUgRGlzdHJpY3QtSUQgKHdlbGNoZSBhdWYgZGllIERpc3RyaWN0LVRhYmVsbGUgdmVyd2Vpc3QpLCBkaWUgRnJlcXVlbnosIHdlbGNoZSBkaWUgSMOkdWZpZ2tlaXQgZGVyIEF1c3N0ZWxsdW5nIGRlciBBYnJlY2hudW5nZW4gYWxzIEthdGVnb3JpZSBiZXNhZ3QsIHVuZCBkYXMgRXJzdGVsbHVuZ3NkYXR1bSBkZXMgQWNjb3VudHMuIERpZSBGcmVxdWVueiBrYW5uIGVpbmUgdm9uIGRyZWkgdmVyc2NoaWVkZW5lbiBXZXJ0ZW4gYW5uZWhtZW4uDQoNCmBgYHtyfQ0KdW5pcXVlKGFjY291bnRzJGZyZXF1ZW5jeSkNCmBgYA0KDQpOYWNoZm9sZ2VuZCBzb2xsZW4gZGllIEZyZXF1ZW56LVdlcnRlIMO8YmVyc2V0enQgdW5kIGRhcyBEYXR1bSBpbiBlaW4gcmljaHRpZ2VzIEZvcm1hdCB0cmFuc2Zvcm1pZXJ0IHdlcmRlbi4gQXVzc2VyZGVtIHNvbGwgZGllIFRhYmVsbGUgYXVmIGZlaGxlbmRlIFdlcnRlIMO8YmVycHLDvGZ0IHdlcmRlbi4NCg0KYGBge3J9DQphY2NvdW50cyRkYXRlIDwtIGFzLkRhdGUoYXMuY2hhcmFjdGVyKGFjY291bnRzJGRhdGUpLCBmb3JtYXQ9ICIleSVtJWQiKQ0KDQphY2NvdW50cyRmcmVxdWVuY3lbYWNjb3VudHMkZnJlcXVlbmN5ID09ICJQT1BMQVRFSyBNRVNJQ05FIl0gICA8LSAibW9udGhseSINCmFjY291bnRzJGZyZXF1ZW5jeVthY2NvdW50cyRmcmVxdWVuY3kgPT0gIlBPUExBVEVLIFRZRE5FIl0gICAgIDwtICJ3ZWVrbHkiDQphY2NvdW50cyRmcmVxdWVuY3lbYWNjb3VudHMkZnJlcXVlbmN5ID09ICJQT1BMQVRFSyBQTyBPQlJBVFUiXSA8LSAiYWZ0ZXJfdHJhbnNhY3Rpb24iDQoNCnN1bShpcy5uYShhY2NvdW50cykpDQpgYGANCg0KRXMgZ2lidCBhbHNvIGtlaW5lIGZlaGxlbmRlIFdlcnRlIGluIGRpZXNlbSBEYXRhZnJhbWUuDQoNCiMjIENhcmRzDQoNCmBgYHtyfQ0Kc2FtcGxlX24oY2FyZHMsIDUpDQpgYGANCkF1Y2ggYmVpIGNhcmQgbXVzcyBkYXMgRGF0dW0gdW1nZXdhbmRlbHQgd2VyZGVuLCBkZXIgemVpdGxpY2hlIFRlaWwgd2lyZCBpZ25vcmllcnQsIGRhIGVyIGltbWVyIDAgaXN0Lg0KDQpgYGB7cn0NCmNhcmRzJGlzc3VlZCA8LSBhcy5EYXRlKGFzLmNoYXJhY3RlcihjYXJkcyRpc3N1ZWQpLCBmb3JtYXQ9ICIleSVtJWQiKQ0KDQpjYXJkcyR0eXBlW2NhcmRzJHR5cGUgPT0gImdvbGQiXSAgICAgPC0gImNsYXNzaWMiDQpjYXJkcyA8LSBmaWx0ZXIoY2FyZHMsIHR5cGUgPT0gImNsYXNzaWMiKQ0KDQpzdW0oaXMubmEoY2FyZHMpKQ0KYGBgDQoNCiMjIENsaWVudHMNCg0KYGBge3J9DQpzYW1wbGVfbihjbGllbnRzLCAxMCkNCmBgYA0KDQpJbiBkZXIgVGFiZWxsZSBleGlzdGllcnQgZGllIFNwYWx0ZSBiaXJ0aF9udW1iZXIsIHdlbGNoZXIgbWFuIGF1ZiBkZW4gZXJzdGVuIEJsaWNrIGRpZSBEYXR1bXNyw6RwcmVzZW50YXRpb24gbmljaHQgYW5zaWVodC4NCkluIGRlciBEb2t1IHdpcmQgZGllIFN0cnVrdHVyIGRldXRsaWNoLCBzaWUgaXN0IGbDvHIgTcOkbm5lciBZWU1NREQgdW5kIGbDvHIgRnJhdWVuIFlZTU1ERCs1MERELg0KSW4gRm9sZ2Ugd2lyZCBkaWUgTnVtbWVyIGluIGlocmUgRGF0dW1zcsOkcHJlc2VudGF0aW9uIGtvbnZlcnRpZXJ0IHVuZCBkaWUgU3BhbHRlICJnZW5kZXIiIGFscyBtYWxlL2ZlbWFsZSBhdWZnZXNjaGzDvHNzZWx0Lg0KWnVkZW0gd2lyZCBkYXMgQWx0ZXIgZGVyIGNsaWVudHMgYmV6b2dlbiBhdWYgZGFzIEphaHIgMTk5OSBoZXJhdXNleHRyYWhpZXJ0LCBkYSBkZXIgRGF0ZW5zYXR6IGF1cyBkaWVzZW0gSmFociBzdGFtbXQuDQpFcyB3aXJkIG5pY2h0IHllYXIoU3lzLkRhdGUoKSkgdmVyd2VuZGV0LCBkYW1pdCBkaWUgRGF0ZW4gYXVjaCBpbiBadWt1bmZ0IGtvbnNpc3RlbnQgYmxpZWJlbi4NCg0KDQoNCmBgYHtyfQ0KIyBNb250aHMgYWJvdmUgMTIgbXVzdCBiZSBmZW1hbGUNCmNsaWVudHMgPC0gbXV0YXRlKGNsaWVudHMsIGdlbmRlciA9IA0KICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKHN1YnN0cihiaXJ0aF9udW1iZXIsIDMsIDQpID4gMTIsICJmZW1hbGUiLCAibWFsZSIpKQ0KDQojIFN1YnN0cmFjdCB0aGUgNTAgdG8gZ2V0IHRoZSBiaXJ0aCBtb250aA0KY2xpZW50cyA8LSBtdXRhdGUoY2xpZW50cywgYmlydGhfbW9udGggPQ0KICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKGFzLm51bWVyaWMoc3Vic3RyKGJpcnRoX251bWJlciwgMywgNCkpID4gMTIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgYXMubnVtZXJpYyhzdWJzdHIoYmlydGhfbnVtYmVyLCAzLCA0KSkgLSA1MCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBhcy5udW1lcmljKHN1YnN0cihiaXJ0aF9udW1iZXIsIDMsIDQpKSkpDQoNCiMgVHJhbnNmb3JtIHRoZSBiaXJ0aF9udW1iZXIgdG8gYSBkYXRlDQpjbGllbnRzIDwtIG11dGF0ZShjbGllbnRzLCBiaXJ0aF9udW1iZXIgPSBwYXN0ZSgiMTkiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3Vic3RyKGJpcnRoX251bWJlciwgMSwgMiksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RyX3BhZChiaXJ0aF9tb250aCwgMiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFkID0gIjAiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN1YnN0cihiaXJ0aF9udW1iZXIsIDUsIDYpLA0KICAgICAgICAgICAgICAgICAgIHNlcCA9ICIiLCBjb2xsYXBzZSA9IE5VTEwpKQ0KY2xpZW50cyRiaXJ0aF9kYXRlIDwtIGFzLkRhdGUoYXMuY2hhcmFjdGVyKGNsaWVudHMkYmlydGhfbnVtYmVyKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmb3JtYXQ9ICIlWSVtJWQiKQ0KDQojIFJlbW92ZSB1bnVzZWQgY29sdW1ucw0KY2xpZW50cyRiaXJ0aF9tb250aCA8LSBOVUxMDQpjbGllbnRzJGJpcnRoX251bWJlciA8LSBOVUxMDQoNCiMgR2V0IHRoZSBhZ2Ugb2YgdGhlIGNsaWVudHMgaW4gdGhlIHllYXIgMTk5OSBhbmQgc2F2ZSBpdCBpbiBhIGNvbHVtbg0KZ2V0X2FnZSA8LSBmdW5jdGlvbihiaXJ0aF9kYXRlKSB7DQogIGJhc2VfeWVhciA8LSA5OQ0KICB5ZWFyIDwtIHN1YnN0cihiaXJ0aF9kYXRlLCAzLCA0KQ0KICByZXN1bHQgPC0gYmFzZV95ZWFyIC0gYXMuaW50ZWdlcih5ZWFyKQ0KICANCiAgcmV0dXJuKHJlc3VsdCkNCn0NCmNsaWVudHMgPC0gY2xpZW50cyAlPiUNCiAgIG11dGF0ZShhZ2UgPSBnZXRfYWdlKGJpcnRoX2RhdGUpKQ0KDQpzdW0oaXMubmEoY2xpZW50cykpDQpgYGANCmBgYHtyfQ0KYWNjb3VudHMNCmNsaWVudHMNCmBgYA0KDQoNCg0KIyMgRGlzcG9zaXRpb25zDQoNCmBgYHtyfQ0Kc2FtcGxlX24oZGlzcG9zaXRpb25zLCA1KQ0KYGBgDQoNCkJlaSBEaXNwb3NpdGlvbnMgc29sbGVuIG51ciBPd25lcnMgdmVyd2VuZGV0IHdlcmRlbiwgZGEgZGllIEFuYWx5c2UgbnVyIEVpZ2VudMO8bWVyIHZvbiBLb250ZW4gYmVoYW5kZWxuIHNvbGwuDQoNCmBgYHtyfQ0KZGlzcG9zaXRpb25zIDwtIGRpc3Bvc2l0aW9ucyAlPiUgZmlsdGVyKHR5cGUgPT0gJ09XTkVSJykNCg0Kc3VtKGlzLm5hKGRpc3Bvc2l0aW9ucykpDQpgYGANCg0KIyMgRGlzdHJpY3RzDQoNCkJlaSBkaXN0cmljdCBzaW5kIGRpZSBTcGFsdGVubmFtZW4gZGVyIFRhYmVsbGUgYWJoYW5kZW4gZ2Vrb21tZW4uDQpIaWVyIHdlcmRlbiBkaWUgVGFiZWxsZW5uYW1lbiB1bWJlbmFubnQsIGdlbcOkc3MgRG9rdS4NCg0KYGBge3J9DQpkaXN0cmljdHMgPC0gcmVuYW1lKGRpc3RyaWN0cywgZGlzdHJpY3RfaWQgPSBBMSwgZGlzdHJpY3RfbmFtZSA9IEEyLCByZWdpb24gPSBBMywgDQogICAgICAgICAgICAgICAgICAgaW5oYWJpdGFudHMgPSBBNCwgbXVuaWNpcGFsaXRpZXNfaW5oYWJpdGFudHNfc21hbGxlcl80OTkgPSBBNSwgDQogICAgICAgICAgICAgICAgICAgbXVuaWNpcGFsaXRpZXNfaW5oYWJpdGFudHNfNTAwX3RvXzE5OTkgPSBBNiwgDQogICAgICAgICAgICAgICAgICAgbXVuaWNpcGFsaXRpZXNfaW5oYWJpdGFudHNfMjAwMF90b185OTk5ID0gQTcsIA0KICAgICAgICAgICAgICAgICAgIG11bmljaXBhbGl0aWVzX2luaGFiaXRhbnRzX2xhcmdlcl8xMDAwMCA9IEE4LCBjaXRpZXMgPSBBOSwgDQogICAgICAgICAgICAgICAgICAgdXJiYW5faW5oYWJpdGFudHNfcmF0aW8gPSBBMTAsIGF2ZXJhZ2Vfc2FsYXJ5ID0gQTExLA0KICAgICAgICAgICAgICAgICAgIHVuZW1wbG95bWVudF9yYXRlXzk1ID0gQTEyLCB1bmVtcGxveW1lbnRfcmF0ZV85NiA9IEExMywNCiAgICAgICAgICAgICAgICAgICBlbnRyZXByZW5ldXJzX3Blcl8xMDAwID0gQTE0LCBjcmltZXNfOTUgPSBBMTUsDQogICAgICAgICAgICAgICAgICAgY3JpbWVzXzk2ID0gQTE2KQ0KDQpzdW0oaXMubmEoZGlzdHJpY3RzKSkNCmBgYA0KDQojIyBUcmFuc2FjdGlvbnMNCg0KYGBge3J9DQpzYW1wbGVfbih0cmFuc2FjdGlvbnMsIDUpDQpgYGANCg0KDQpJbiBkZW4gVHJhbnNha3Rpb25lbiBtdXNzIGRhcyBEYXR1bSBnZW3DpHNzIEZvcm1hdCBZWU1NREQga29udmVydGllcnQgd2VyZGVuLg0KDQpgYGB7cn0NCiMgUmVuYW1lIGtfc3ltYm9sDQp0cmFuc2FjdGlvbnMgPC0gcmVuYW1lKHRyYW5zYWN0aW9ucywgYygiY2hhcmFjdGVyaXphdGlvbiIgPSAia19zeW1ib2wiKSkgDQoNCiMgQ2hhbmdlIGZvcm1hdHMNCnRyYW5zYWN0aW9ucyRkYXRlIDwtIGFzLkRhdGUoYXMuY2hhcmFjdGVyKHRyYW5zYWN0aW9ucyRkYXRlKSwgZm9ybWF0PSAiJXklbSVkIikNCnRyYW5zYWN0aW9ucyRhbW91bnQgPC0gYXMubnVtZXJpYyh0cmFuc2FjdGlvbnMkYW1vdW50KQ0KdHJhbnNhY3Rpb25zJGJhbGFuY2UgPC0gYXMubnVtZXJpYyh0cmFuc2FjdGlvbnMkYmFsYW5jZSkNCg0KIyBUcmFuc2xhdGUgdmFsdWVzDQp0cmFuc2FjdGlvbnMkdHlwZVt0cmFuc2FjdGlvbnMkdHlwZSA9PSAiUFJJSkVNIl0gPC0gImNyZWRpdCINCnRyYW5zYWN0aW9ucyR0eXBlW3RyYW5zYWN0aW9ucyR0eXBlID09ICJWWURBSiJdICA8LSAid2l0aGRyYXdhbCINCnRyYW5zYWN0aW9ucyR0eXBlW3RyYW5zYWN0aW9ucyR0eXBlID09ICJWWUJFUiJdICA8LSAid2l0aGRyYXdhbCINCg0KdHJhbnNhY3Rpb25zJG9wZXJhdGlvblt0cmFuc2FjdGlvbnMkb3BlcmF0aW9uID09ICJWS0xBRCJdICAgICAgICAgIDwtICJjYXNoIGNyZWRpdCINCnRyYW5zYWN0aW9ucyRvcGVyYXRpb25bdHJhbnNhY3Rpb25zJG9wZXJhdGlvbiA9PSAiUFJFVk9EIFogVUNUVSJdICA8LSAiY29sbGVjdGlvbiINCnRyYW5zYWN0aW9ucyRvcGVyYXRpb25bdHJhbnNhY3Rpb25zJG9wZXJhdGlvbiA9PSAiVllCRVIiXSAgICAgICAgICA8LSAiY2FzaCB3aXRoZHJhd2FsIg0KdHJhbnNhY3Rpb25zJG9wZXJhdGlvblt0cmFuc2FjdGlvbnMkb3BlcmF0aW9uID09ICIgIl0gICAgICAgICAgICAgIDwtICJ1bmtub3duIg0KdHJhbnNhY3Rpb25zJG9wZXJhdGlvblt0cmFuc2FjdGlvbnMkb3BlcmF0aW9uID09ICJQUkVWT0QgTkEgVUNFVCJdIDwtICJyZW1pdHRhbmNlIg0KdHJhbnNhY3Rpb25zJG9wZXJhdGlvblt0cmFuc2FjdGlvbnMkb3BlcmF0aW9uID09ICJWWUJFUiBLQVJUT1UiXSAgIDwtICJjYXJkIHdpdGhkcmF3YWwiDQoNCnRyYW5zYWN0aW9ucyRjaGFyYWN0ZXJpemF0aW9uW3RyYW5zYWN0aW9ucyRjaGFyYWN0ZXJpemF0aW9uID09ICIgIl0gPC0gInVua25vd24iDQp0cmFuc2FjdGlvbnMkY2hhcmFjdGVyaXphdGlvblt0cmFuc2FjdGlvbnMkY2hhcmFjdGVyaXphdGlvbiA9PSAiRFVDSE9EIl0gPC0gInBlbnNpb24iDQp0cmFuc2FjdGlvbnMkY2hhcmFjdGVyaXphdGlvblt0cmFuc2FjdGlvbnMkY2hhcmFjdGVyaXphdGlvbiA9PSAiVVJPSyJdIDwtICJpbnRlcmVzdCINCnRyYW5zYWN0aW9ucyRjaGFyYWN0ZXJpemF0aW9uW3RyYW5zYWN0aW9ucyRjaGFyYWN0ZXJpemF0aW9uID09ICJTSVBPIl0gPC0gImhvdXNlaG9sZCINCnRyYW5zYWN0aW9ucyRjaGFyYWN0ZXJpemF0aW9uW3RyYW5zYWN0aW9ucyRjaGFyYWN0ZXJpemF0aW9uID09ICJTTFVaQlkiXSA8LSAicGF5bWVudCBzdGF0ZW1lbnQiDQp0cmFuc2FjdGlvbnMkY2hhcmFjdGVyaXphdGlvblt0cmFuc2FjdGlvbnMkY2hhcmFjdGVyaXphdGlvbiA9PSAiUE9KSVNUTkUiXSA8LSAiaW5zdXJhbmNlIg0KdHJhbnNhY3Rpb25zJGNoYXJhY3Rlcml6YXRpb25bdHJhbnNhY3Rpb25zJGNoYXJhY3Rlcml6YXRpb24gPT0gIlNBTktDLiBVUk9LIl0gIDwtICJuZWdfaW50ZXJlc3QiDQp0cmFuc2FjdGlvbnMkY2hhcmFjdGVyaXphdGlvblt0cmFuc2FjdGlvbnMkY2hhcmFjdGVyaXphdGlvbiA9PSAiVVZFUiJdICA8LSAibG9hbl9wYXkiDQoNCnN1bShpcy5uYSh0cmFuc2FjdGlvbnMpKQ0KYGBgDQoNCiMjIE9yZGVycw0KDQpgYGB7cn0NCnNhbXBsZV9uKG9yZGVycywgNSkNCmBgYA0KDQpgYGB7cn0NCiMgUmVuYW1lIGNvbHVtbiBrX3N5bWJvbA0Kb3JkZXJzIDwtIHJlbmFtZShvcmRlcnMsICJjaGFyYWN0ZXJpemF0aW9uIiA9ICJrX3N5bWJvbCIpIA0KDQojIFRyYW5zbGF0ZSBjb2x1bW4gY2hhcmFjdGVyaXphdGlvbg0Kb3JkZXJzJGNoYXJhY3Rlcml6YXRpb25bb3JkZXJzJGNoYXJhY3Rlcml6YXRpb24gPT0gIlNJUE8iXSAgICAgPC0gImhvdXNlaG9sZCINCm9yZGVycyRjaGFyYWN0ZXJpemF0aW9uW29yZGVycyRjaGFyYWN0ZXJpemF0aW9uID09ICJVVkVSIl0gICAgIDwtICJsb2FuIg0Kb3JkZXJzJGNoYXJhY3Rlcml6YXRpb25bb3JkZXJzJGNoYXJhY3Rlcml6YXRpb24gPT0gIlBPSklTVE5FIl0gPC0gImluc3VyYW5jZSINCm9yZGVycyRjaGFyYWN0ZXJpemF0aW9uW29yZGVycyRjaGFyYWN0ZXJpemF0aW9uID09ICJMRUFTSU5HIl0gIDwtICJsZWFzaW5nIg0KDQojIENhdGVnb3JpemUgTkEgYXMgdW5rbm93bg0Kb3JkZXJzJGNoYXJhY3Rlcml6YXRpb25baXMubmEob3JkZXJzJGNoYXJhY3Rlcml6YXRpb24pXSA8LSAidW5rbm93biINCg0Kb3JkZXJzJGFtb3VudCA8LSBhcy5udW1lcmljKG9yZGVycyRhbW91bnQpDQoNCnN1bShpcy5uYShsb2FucykpDQpgYGANCg0KDQojIyBMb2Fucw0KDQpgYGB7cn0NCnNhbXBsZV9uKGxvYW5zLCA1KQ0KYGBgDQoNCmBgYHtyfQ0KbG9hbnMkZGF0ZSA8LSBhcy5EYXRlKGFzLmNoYXJhY3Rlcihsb2FucyRkYXRlKSwgZm9ybWF0PSAiJXklbSVkIikNCmxvYW5zJHBheW1lbnRzIDwtIGFzLm51bWVyaWMobG9hbnMkcGF5bWVudHMpDQpsb2FucyRhbW91bnQgPC0gYXMubnVtZXJpYyhsb2FucyRhbW91bnQpDQoNCiMgTWFrZSBjb2x1bW4gc3RhdHVzIGh1bWFuIHJlYWRhYmxlDQpsb2FucyRzdGF0dXNbbG9hbnMkc3RhdHVzID09ICJBIl0gPC0gImZpbmlzaGVkX3BheWVkIg0KbG9hbnMkc3RhdHVzW2xvYW5zJHN0YXR1cyA9PSAiQiJdIDwtICJmaW5pc2hlZF9ub3RfcGF5ZWQiDQpsb2FucyRzdGF0dXNbbG9hbnMkc3RhdHVzID09ICJDIl0gPC0gInJ1bm5pbmdfb2siDQpsb2FucyRzdGF0dXNbbG9hbnMkc3RhdHVzID09ICJEIl0gPC0gInJ1bm5pbmdfaW5fZGVidCINCg0Kc3VtKGlzLm5hKGxvYW5zKSkNCmBgYA0KDQoNCiMgWnVzYW1tZW5mw7xnZW4gZGVyIERhdGFmcmFtZXMNCg0KSW4gZGllc2VtIEFic2Nobml0dCB3ZXJkZW4gZGllIHZlcnNjaGllZGVuZW4gVGFiZWxsZW4genVzYW1tZW4gZ2VzZXR6dC4NCkRhYmVpIHdlcmRlbiBsb2FuLCBjYXJkcyB1bmQgZGlzdHJpY3QgaXQgbGVmdCBqb2luIGFuZ2Vow6RuZ3QsIGRhbWl0IGZlaGxlbmRlIFNwYWx0ZW4gbmljaHQgZGVuIERhdGVuc2F0eiB2ZXJrbGVpbmVybi4NCkRpZSBUcmFuc2FrdGlvbnNkYXRlbiB3ZXJkZW4gaGllciBub2NoIG5pY2h0IHp1c2FtbWVuZ2Vmw7xocnQuDQoNCmBgYHtyfQ0KIyBDbGllbnRzIG1pdCBkaXNwb3NpdGlvbnMNCmZ1bGwgPC0gaW5uZXJfam9pbihjbGllbnRzLCBkaXNwb3NpdGlvbnMsIGJ5ID0gImNsaWVudF9pZCIsIHN1ZmZpeCA9IGMoIi5jbGllbnQiLCAiLmRpc3Bvc2l0aW9ucyIpKQ0Kc3VtKGR1cGxpY2F0ZWQoZnVsbCRjbGllbnRfaWQpKQ0KDQojIEZ1bGwgbWl0IGFjY291bnQNCmZ1bGwgPC0gaW5uZXJfam9pbihmdWxsLCBhY2NvdW50cywgYnkgPSAiYWNjb3VudF9pZCIsIHN1ZmZpeCA9IGMoIiIsICIuYWNjb3VudHMiKSkNCnN1bShkdXBsaWNhdGVkKGZ1bGwkYWNjb3VudF9pZCkpDQoNCiMgRnVsbCBtaXQgbG9hbg0Kc3VtKGR1cGxpY2F0ZWQobG9hbnMkYWNjb3VudF9pZCkpDQpmdWxsIDwtIGxlZnRfam9pbihmdWxsLCBsb2FucywgYnkgPSAiYWNjb3VudF9pZCIsIHN1ZmZpeCA9IGMoIiIsICIubG9hbnMiKSkNCg0KIyBGdWxsIG1pdCBjYXJkcw0KZnVsbCA8LSBsZWZ0X2pvaW4oZnVsbCwgY2FyZHMsIGJ5ID0gImRpc3BfaWQiLCBzdWZmaXggPSBjKCIiLCAiLmNhcmRzIikpDQpzdW0oZHVwbGljYXRlZChjYXJkcyRkaXNwX2lkKSkNCg0KIyBEaXN0cmljdCBJbmZvcm1hdGlvbnMgZm9yIGNsaWVudA0KZnVsbCA8LSBsZWZ0X2pvaW4oZnVsbCwgZGlzdHJpY3RzLCBieSA9ICJkaXN0cmljdF9pZCIpDQoNCiMgRGlzdHJpY3QgaW5mb3JtYXRpb25zIGZvciBjYXJkDQpmdWxsIDwtIGxlZnRfam9pbihmdWxsLCBkaXN0cmljdHMsIGJ5ID0gYygiZGlzdHJpY3RfaWQuYWNjb3VudHMiPSJkaXN0cmljdF9pZCIpLCBzdWZmaXggPSBjKCIiLCAiLmFjY291bnRzIikpDQoNCnNhbXBsZV9uKGZ1bGwsIDUpDQpgYGANCg0KSnVnZW5kbGljaGUgdW5kIFBlcnNvbmVuLCB3ZWxjaGUgd8OkaHJlbmQgZGVzIFplaXRyYXVtcyBkZXMgRGF0ZW5zYXR6ZXMgZXJzdCBlcndhY2hzZW4gd29yZGVuIHNpbmQsIHNvbGxlbiBuaWNodCBpbiBkaWUgQXVzd2VydHVuZyBlaW5mbGllc3Nlbi4NCkRhIHNpY2hlciBkZXIgRGF0ZW5zYXR6IMO8YmVyIGVpbmVuIFplaXRyYXVtIHZvbiBzZWNocyBKYWhyZW4gZXJzdHJlY2t0IHdlcmRlbiBhbGxlIENsaWVudHMgasO8bmdlciBhbHMgMjUgSmFocmUgaGVyYXVzZ2VmaWx0ZXJ0Lg0KDQpgYGB7cn0NCmZ1bGwgPC0gZnVsbCAlPiUgZmlsdGVyKGFnZSA+PSAyNSkNCmBgYA0KDQpgYGB7cn0NCmZ1bGwNCmBgYA0KDQoNCg0KQWxzIG7DpGNoc3RlcyB3ZXJkZW4gYWxsZSBaZWlsZW4gbWl0IEtyZWRpdGthcnRlbmvDpHVmZXJuIHZvbiBkZW4gTmljaHQtS8OkdWZlcm4gZ2V0cmVubnQNCg0KDQpgYGB7cn0NCmhhc19jYXJkX2Z1bmN0aW9uIDwtIGZ1bmN0aW9uKHgpIHsNCiAgaWYgKGlzLm5hKHgpKSB7DQogICAgcmV0dXJuKEZBTFNFKQ0KICB9IGVsc2Ugew0KICAgIHJldHVybihUUlVFKQ0KICB9DQp9DQoNCiMgRXJzdGVsbGUgZGllIG5ldWUgU3BhbHRlICJoYXNfY2FyZCIgbWl0IGRlciBhcHBseSgpLUZ1bmt0aW9uIHVuZCBkZXIgb2JlbiBkZWZpbmllcnRlbiBGdW5rdGlvbg0KZnVsbCRoYXNfY2FyZCA8LSBzYXBwbHkoZnVsbFssICJjYXJkX2lkIl0sIGhhc19jYXJkX2Z1bmN0aW9uKQ0KZnVsbCA8LSBmdWxsICU+JSBzZWxlY3QoLWNhcmRfaWQsIC10eXBlLmNhcmRzKQ0KDQpjYXJkX2J1eWVycyA8LSBmdWxsICU+JSBmaWx0ZXIoaGFzX2NhcmQgPT0gVFJVRSkNCg0Kbm9uX2J1eWVycyA8LSBmdWxsICU+JSBmaWx0ZXIoaGFzX2NhcmQgPT0gRkFMU0UpDQpgYGANCg0KSmV0enQga8O2bm5lbiB3aXIgbm9jaCBlaW5pZ2UgVmFyaWFiZWxuIGVudGZlcm5lbiwgd2VsY2hlIGtlaW5lbiBFaW5mbHVzcyBhdWYgZGFzIE1vZGVsbCBoYWJlbiBzb2xsdGVuLiANCg0KIyBBdWZzdW1taWVyZW4gZGVyIFRyYW5zYWt0aW9uZW4NCg0KYGBge3J9DQpzYW1wbGVfbih0cmFuc2FjdGlvbnMsIDUpDQpgYGANCg0KDQpCZWkgZGVuIFRyYW5zYWt0aW9uZW4gaXN0IGpld2VpbHMgZGllIG5ldWUgQmFsYW5jZSB1bmQgZGVyIEJldHJhZyBkZXIgVHJhbnNha3Rpb24gYW5nZWdlYm4uIERhcyBQcm9ibGVtIGRhYmVpIGlzdCwgZGFzcyBhbGxlIEJldHLDpGdlIHBvc2l0aXYgc2luZCwgYXVjaCB3ZW5uIHNpZSBlaWdlbnRsaWNoIGFiZ2V6b2dlbiB3ZXJkZW4uIA0KDQpgYGB7cn0NCmRmIDwtIHRyYW5zYWN0aW9ucw0KDQojIEtvbnZlcnRpZXJlbiBTaWUgZGFzICdkYXRlJy1GZWxkIGluIGVpbiBEYXR1bQ0KZGYkZGF0ZSA8LSBhcy5EYXRlKGRmJGRhdGUpDQoNCiMgU29ydGllcmVuIFNpZSBkYXMgRGF0YWZyYW1lIG5hY2ggTnV0emVyIHVuZCBEYXR1bQ0KZGYgPC0gZGZbb3JkZXIoZGYkYWNjb3VudF9pZCwgZGYkZGF0ZSksIF0NCg0KIyBHcnVwcGllcmVuIFNpZSBkYXMgRGF0YWZyYW1lIG5hY2ggTnV0emVyDQpkZiA8LSBncm91cF9ieShkZiwgYWNjb3VudF9pZCkNCg0KIyBJdGVyaWVyZW4gU2llIMO8YmVyIGplZGVuIE51dHplciB1bmQgYmVhcmJlaXRlbiBTaWUgZGllIFRyYW5zYWt0aW9uZW4NCmRmIDwtIGRmICU+JSANCiAgc3VtbWFyaXplKHRyYW5zYWN0aW9ucyA9IHsNCiAgICAjIEbDvGdlbiBTaWUgZWluZSBTcGFsdGUgbWl0IGRlbSB2b3JoZXJpZ2VuIEtvbnRvc3RhbmQgaGluenUNCiAgICBwcmV2X2JhbGFuY2UgPC0gaWZlbHNlKHJvd19udW1iZXIoKSA9PSAxLCBOQSwgbGFnKGJhbGFuY2UsIG9yZGVyX2J5ID0gZGF0ZSkpDQoNCiAgICAjIEJlcmVjaG5lbiBTaWUgZGVuIFVudGVyc2NoaWVkIHp3aXNjaGVuIGRlbSB2b3JoZXJpZ2VuIEtvbnRvc3RhbmQgdW5kIGRlbSBha3R1ZWxsZW4gS29udG9zdGFuZA0KICAgIGRpZmZlcmVuY2UgPC0gYmFsYW5jZSAtIHByZXZfYmFsYW5jZQ0KDQogICAgIyBGw7xnZW4gU2llIGVpbmUgU3BhbHRlIG1pdCBkZXIgVHJhbnNha3Rpb25zYXJ0IGhpbnp1DQogICAgdHlwZSA8LSAiYWRkIg0KICAgIHR5cGVbZGlmZmVyZW5jZSA8IDBdIDwtICJzdWJ0cmFjdCINCg0KICAgICMgRXJzdGVsbGVuIFNpZSBkYXMgRGF0YWZyYW1lIG1pdCBkZW4gVHJhbnNha3Rpb25lbiBmw7xyIGplZGVuIE51dHplcg0KICAgIHRyYW5zYWN0aW9uc19kZiA8LSBkYXRhLmZyYW1lKGFtb3VudCwgZGF0ZSwgYmFsYW5jZSwgcHJldl9iYWxhbmNlLCBkaWZmZXJlbmNlLCB0eXBlKQ0KICAgIHRyYW5zYWN0aW9uc19kZg0KICB9KSAlPiUNCiAgdW5ncm91cCgpDQoNCnRyYW5zYWN0aW9ucyA8LSB1bm5lc3QoZGYsIHRyYW5zYWN0aW9ucykNCg0KIyBIaW56dWbDvGdlbiBkZXMgZXJzdGVuIGFtb3VudHMgYmVpIGplZGVtIEFjY291bnQNCnRyYW5zYWN0aW9ucyRkaWZmZXJlbmNlIDwtIGlmZWxzZShpcy5uYSh0cmFuc2FjdGlvbnMkZGlmZmVyZW5jZSkgJiBpcy5uYSh0cmFuc2FjdGlvbnMkcHJldl9iYWxhbmNlKSAmICh0cmFuc2FjdGlvbnMkYW1vdW50ID09IHRyYW5zYWN0aW9ucyRiYWxhbmNlKSwgdHJhbnNhY3Rpb25zJGFtb3VudCwgdHJhbnNhY3Rpb25zJGRpZmZlcmVuY2UpDQoNCnRyYW5zYWN0aW9ucyRhbW91bnQgPC0gTlVMTA0KDQp0cmFuc2FjdGlvbnMNCmBgYA0KDQojIyBadXNhbW1lbmZhc3NlbiBkZXIgVHJhbnNha3Rpb25lbiBmw7xyIENhcmQgQnV5ZXJzDQoNClVtIGRpZSBUcmFuc2FrdGlvbnMtRGF0ZW4gaW4gdW5zZXJlbiBNb2RlbGxlbiBicmF1Y2hlbiB6dSBrw7ZubmVuLCBtdXNzIGbDvHIgamVkZW4gS3VuZGUgZWluIFJvbGx1cC1GZW5zdGVyIGVyc3RlbGx0IHdlcmRlbi4gRGllcyBmYXNzdCBkaWUgVHJhbnNha3Rpb25lbiBkZXIgenfDtmxmIE1vbmF0ZSB2b3IgZGVtIEVyaGFsdCBlaW5lciBLcmVkaXRrYXJ0ZSB6dXNhbW1lbiAobWludXMgZWluZW4gTW9uYXQgSW5wdXQgTGFnKS4gQXVmIGRpZXNlbiBNb25hdGVuIHdlcmRlbiBkaWUgVHJhbnNha3Rpb25lbiB6dXNhbW1lbmdlZmFzc3QuDQoNCkFscyBlcnN0ZXMgd2VyZGVuIGRpZSBUcmFuc2FrdGlvbmVuIHZvbiBLdW5kZW4gaGVyYXVzZ2VmaWx0ZXJ0LCB3ZWxjaGUgZWluZSBLcmVkaXRrYXJ0ZSBoYWJlbi4NCg0KYGBge3J9DQphY2NvdW50X2lkcyA8LSBjYXJkX2J1eWVycyRhY2NvdW50X2lkDQpidXllcl90cmFuc2FjdGlvbnMgPC0gdHJhbnNhY3Rpb25zW3RyYW5zYWN0aW9ucyRhY2NvdW50X2lkICVpbiUgYWNjb3VudF9pZHMsXQ0KYGBgDQoNCkRhcyBpc3N1ZWQtRGF0dW0gc29sbCB6dSBkZW4gVHJhbnNha3Rpb25lbiBoaW56dWdlZsO8Z3Qgd2VyZGVuLCBkYW1pdCBkaWVzZSBmw7xyIGplZGVuIEt1bmRlbiBlaW56ZWxuIGdlZmlsdGVydCB3ZXJkZW4ga8O2bm5lbi4NCg0KYGBge3J9DQpidXllcl90cmFuc2FjdGlvbnMgPC0gbWVyZ2UoYnV5ZXJfdHJhbnNhY3Rpb25zLCBmdWxsWywgYygiYWNjb3VudF9pZCIsICJpc3N1ZWQiKV0sIGJ5PSJhY2NvdW50X2lkIikNCmBgYA0KDQpOdW4gc29sbGVuIFRyYW5zYWt0aW9uZW4gc28gZ2VmaWx0ZXJ0IHdlcmRlbiwgZGFzcyBudXIgbm9jaCBUcmFuc2FrdGlvbmVuIHp3aXNjaGVuIDEzIE1vbmF0ZW4gdW5kIDEgTW9uYXQgdm9yIGRlbSBJc3N1ZWQgRGF0dW0gdm9ya29tbWVuLg0KDQpgYGB7cn0NCmZpbHRlcmVkX2RmIDwtIGJ1eWVyX3RyYW5zYWN0aW9ucyAlPiUNCiAgZmlsdGVyKGRhdGUgPj0gYXMuRGF0ZShwYXN0ZTAoZm9ybWF0KGlzc3VlZCAtIG1vbnRocygxMyksICIlWS0lbSIpLCAiLTAxIikpICYNCiAgICAgICAgIGRhdGUgPD0gYXMuRGF0ZShwYXN0ZTAoZm9ybWF0KGlzc3VlZCAtIG1vbnRocygxKSwgIiVZLSVtIiksICItMDEiKSkgLSAxKQ0KYGBgDQoNCkF1ZiBkaWVzZW4gRGF0ZW4gd2lyZCBlaW5lIEdydXBwaWVydW5nIGFuaGFuZCBkZXIgYWNjb3VudF9pZCB1bmQgZGVzIE1vbmF0cyBnZW1hY2h0IHdlcmRlbi4gRGllIFdlcnRlIGluIGRpZmZlcmVuY2UgdW5kIGJhbGFuY2Ugd2VyZGVuIHp1IHZlcnNjaGllZGVuZW4gTWV0cmlrZW4genVzYW1tZW5nZWZhc3N0Og0KQXVmIGJlaWRlbiBXZXJ0ZW4gZXJmYXNzZW4gd2lyIGRhcyBNaW5pbXVtLCBkYXMgTWF4aW11bSwgZGVuIER1cmNoc2Nobml0dCwgZGVuIE1lZGlhbiB1bmQgZGllIFN0YW5kYXJkYWJ3ZWljaHVuZy4gQmVpIGRlciBiYWxhbmNlIGVyZmFzc2VuIHdpciBkaWUgZXJzdGUgdW5kIGRpZSBsZXR6dGUgQmFsYW5jZSBkZXMgTW9uYXRzIHVuZCBiZWkgZGlmZmVyZW5jZSBkaWUgQW56YWhsIHBvc2l0aXZlIHVuZCBuZWdhdGl2ZSBkaWZmZXJlbmNlcy4NCg0KYGBge3J9DQpzdW1tYXJ5X2RmIDwtIGZpbHRlcmVkX2RmICU+JQ0KICBncm91cF9ieShhY2NvdW50X2lkLCBtb250aCA9IGZvcm1hdChkYXRlLCAiJVktJW0iKSkgJT4lDQogIHN1bW1hcmlzZSgNCiAgICBtYXhfZGlmZmVyZW5jZSA9IG1heChkaWZmZXJlbmNlKSwNCiAgICBtaW5fZGlmZmVyZW5jZSA9IG1pbihkaWZmZXJlbmNlKSwNCiAgICBtYXhfYmFsYW5jZSA9IG1heChiYWxhbmNlKSwNCiAgICBtaW5fYmFsYW5jZSA9IG1pbihiYWxhbmNlKSwNCiAgICBpbml0aWFsX2JhbGFuY2UgPSBmaXJzdChiYWxhbmNlKSwNCiAgICBlbmRfYmFsYW5jZSA9IGxhc3QoYmFsYW5jZSksDQogICAgbWVhbl9iYWxhbmNlID0gbWVhbihiYWxhbmNlKSwNCiAgICBtZWRpYW5fYmFsYW5jZSA9IG1lZGlhbihiYWxhbmNlKSwNCiAgICBzdGRfYmFsYW5jZSA9IHNkKGJhbGFuY2UpLA0KICAgIG1lYW5fZGlmZmVyZW5jZSA9IG1lYW4oZGlmZmVyZW5jZSksDQogICAgbWVkaWFuX2RpZmZlcmVuY2UgPSBtZWRpYW4oZGlmZmVyZW5jZSksDQogICAgc3RkX2RpZmZlcmVuY2UgPSBzZChkaWZmZXJlbmNlKSwNCiAgICBjb3VudF9wb3NpdGl2ZV9kaWZmZXJlbmNlID0gc3VtKGRpZmZlcmVuY2UgPiAwKSwNCiAgICBjb3VudF9uZWdhdGl2ZV9kaWZmZXJlbmNlID0gc3VtKGRpZmZlcmVuY2UgPCAwKQ0KICApDQpzdW1tYXJ5X2RmIDwtIHN1bW1hcnlfZGYgJT4lDQogIGFycmFuZ2UoYWNjb3VudF9pZCkNCnN1bW1hcnlfZGYNCmBgYA0KDQpKZXR6dCBoYWJlbiB3aXIgZsO8ciBqZWRlIGFjY291bnRfaWQgZWluZSDDnGJlcnNpY2h0IMO8YmVyIGRpZSAxMiBNb25hdGUgdm9yIGRlbSBLYXJ0ZW5lcmhhbHQuIERhIGVzIGFiZXIgc2VpbiBrw7ZubnRlLCBkYXNzIGVzIEt1bmRlbiBnaWJ0LCB3ZWxjaGUgbmljaHQgamVkZW4gTW9uYXQgZWluZSBUcmFuc2FrdGlvbiBoYXR0ZW4gb2RlciBkaWUgS3JlZGl0a2FydGUgYmVyZWl0cyBpbSBlcnN0ZW4gSmFociBlcmhhbHRlbiBoYWJlbiwga29udHJvbGxpZXJlbiB3aXIgZGllcyBub2NoLg0KDQpgYGB7cn0NCiMgS29udHJvbGxlLCBvYiBmw7xyIGplZGVuIGFjY291bnRfaWQgMTIgbW9uYXRlIHZvcmhhbmRlbiBzaW5kDQptb250aF9jb3VudHMgPC0gc3VtbWFyeV9kZiAlPiUNCiAgZ3JvdXBfYnkoYWNjb3VudF9pZCkgJT4lDQogIHN1bW1hcmlzZShtb250aF9jb3VudCA9IG5fZGlzdGluY3QobW9udGgpKQ0KDQojIFByw7xmZSwgb2IgamVkZXMgYWNjb3VudF9pZCAxMiBNb25hdGUgaGF0DQptb250aF9jb3VudHMgPC0gbW9udGhfY291bnRzICU+JSBmaWx0ZXIobW9udGhfY291bnQgIT0gMTIpDQptb250aF9jb3VudHMNCg0KYGBgDQoxNjIgS3VuZGVuIGhhYmVuIGFsc28ga2VpbmUgMTIga29udGludWllcmxpY2hlbiBNb25hdGUgbWl0IFRyYW5zYWt0aW9uZW4sIGJldm9yIHNpZSBlaW5lIEthcnRlIGJla29tbWVuLiBXaXIgZmlsdGVybiBkaWVzZSBLdW5kZW4gcmF1cy4NCg0KYGBge3J9DQpzdW1tYXJ5X2RmIDwtIHN1YnNldChzdW1tYXJ5X2RmLCAhYWNjb3VudF9pZCAlaW4lIG1vbnRoX2NvdW50cyRhY2NvdW50X2lkKQ0KYGBgDQoNCkFscyBuw6RjaHN0ZXMgbnVtbWVyaWVyZW4gd2lyIGRpZSBNb25hdGUgcHJvIGFjY291bnRfaWQgdm9uIDEgYmlzIDEyIGR1cmNoLCB1bSBkYW5hY2ggd2VpdGVyIGRhbWl0IGFyYmVpdGVuIHp1IGvDtm5uZW4uDQoNCmBgYHtyfQ0KIyBTb3J0aWVyZW4gbmFjaCBhY2NvdW50X2lkIHVuZCBNb25hdA0Kc3VtbWFyeV9kZiA8LSBzdW1tYXJ5X2RmW29yZGVyKHN1bW1hcnlfZGYkYWNjb3VudF9pZCwgcmV2KHN1bW1hcnlfZGYkbW9udGgpKSxdDQoNCiMgSGluenVmw7xnZW4gZGVyIE1vbmF0c251bW1lcg0Kc3VtbWFyeV9kZiRncm91cF9pZCA8LSBhdmUoc2VxX2Fsb25nKHN1bW1hcnlfZGYkYWNjb3VudF9pZCksIHN1bW1hcnlfZGYkYWNjb3VudF9pZCwgRlVOID0gZnVuY3Rpb24oeCkge3h9KQ0Kc3VtbWFyeV9kZiRtb250aF9udW1iZXIgPC0gMTINCg0KZm9yIChpIGluIDI6bnJvdyhzdW1tYXJ5X2RmKSkgew0KICBpZiAoc3VtbWFyeV9kZiRhY2NvdW50X2lkW2ldICE9IHN1bW1hcnlfZGYkYWNjb3VudF9pZFtpLTFdKSB7DQogICAgc3VtbWFyeV9kZiRtb250aF9udW1iZXJbaV0gPC0gMTINCiAgfSBlbHNlIHsNCiAgICBzdW1tYXJ5X2RmJG1vbnRoX251bWJlcltpXSA8LSBzdW1tYXJ5X2RmJG1vbnRoX251bWJlcltpLTFdIC0gMQ0KICB9DQp9DQoNCiMgRW50ZmVybmUgZGllIFNwYWx0ZSBncm91cF9pZA0Kc3VtbWFyeV9kZiRncm91cF9pZCA8LSBOVUxMDQpzdW1tYXJ5X2RmJG1vbnRoIDwtIE5VTEwNCmBgYA0KDQpOdW4gbcO2Y2h0ZW4gd2lyIGFsbGUgSW5mb3JtYXRpb25lbiBwcm8gYWNjb3VudF9pZCBhdWYgZWluZXIgWmVpbGUgaGFiZW4uIERhZsO8ciBicmF1Y2hlbiB3aXIgcGl2b3Rfd2lkZXIuIFNvIGhhYmVuIHdpciBqZWRlIEtlbm56YWhsIHp3w7ZsZiBtYWwgYWxzIEtvbG9ubmUsIGplZGVzIE1hbCBtaXQgZGVyIHZvcmhlciBlcnN0ZWxsdGVuIE1vbmF0c251bW1lciBhbHMgU3VmZml4Lg0KDQpgYGB7cn0NCnN1bW1hcnlfZGZfYnV5ZXJzIDwtIHN1bW1hcnlfZGYgJT4lDQogIGdyb3VwX2J5KGFjY291bnRfaWQpICU+JQ0KICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gbW9udGhfbnVtYmVyLA0KICAgICAgICAgICAgICB2YWx1ZXNfZnJvbSA9IGMobWF4X2RpZmZlcmVuY2UsIG1pbl9kaWZmZXJlbmNlLCBtYXhfYmFsYW5jZSwgbWluX2JhbGFuY2UsIGluaXRpYWxfYmFsYW5jZSwgZW5kX2JhbGFuY2UsIG1lYW5fYmFsYW5jZSwgbWVkaWFuX2JhbGFuY2UsIHN0ZF9iYWxhbmNlLCBtZWRpYW5fYmFsYW5jZSwgc3RkX2JhbGFuY2UsIG1lYW5fZGlmZmVyZW5jZSwgbWVkaWFuX2RpZmZlcmVuY2UsIHN0ZF9kaWZmZXJlbmNlLCBjb3VudF9wb3NpdGl2ZV9kaWZmZXJlbmNlLCBjb3VudF9uZWdhdGl2ZV9kaWZmZXJlbmNlKSkNCg0Kc3VtbWFyeV9kZl9idXllcnMgPC0gbWVyZ2Uoc3VtbWFyeV9kZl9idXllcnMsIGNhcmRfYnV5ZXJzLCBieSA9ICJhY2NvdW50X2lkIikNCmBgYA0KDQpgYGB7cn0NCnN1bW1hcnlfZGZfYnV5ZXJzDQpgYGANCg0KDQojIyBGaW5kZW4gdm9uIMOkaG5saWNoZW4gTnV0emVybg0KDQpadSBqZWRlbSBLYXJ0ZW5rw6R1ZmVyIHNvbGwgbnVuIGVpbiDDpGhubGljaGVyIE5pY2h0a8OkdWZlciBnZWZ1bmRlbiB3ZXJkZW4NCg0KYGBge3J9DQojIEVyc3RlbGxlIGVpbiBsZWVyZXMgRGF0YUZyYW1lICJzaW1pbGFyX25vbl9idXllcnMiDQpzaW1pbGFyX25vbl9idXllcnMgPC0gZGF0YS5mcmFtZSgpDQoNCiMgSXRlcmllcmUgw7xiZXIgamVkZW4gS3VuZGVuIGltIERhdGFGcmFtZSAiYnV5ZXJzIg0KZm9yIChpIGluIDE6bnJvdyhjYXJkX2J1eWVycykpIHsNCiAgIyBXw6RobGUgZGVuIGFrdHVlbGxlbiBLdW5kZW4gYXVzIGRlbSBEYXRhRnJhbWUgImJ1eWVycyINCiAgY3VycmVudF9idXllciA8LSBjYXJkX2J1eWVyc1tpLCBdDQogIA0KICAjIFfDpGhsZSBkaWUgS3VuZGVuIGF1cyBkZW0gRGF0YUZyYW1lICJub25fYnV5ZXJzIiBhdXMsIGRpZSBkYXMgZ2xlaWNoZSBHZXNjaGxlY2h0IGhhYmVuIHVuZCBtw7ZnbGljaHN0IGdsZWljaCBhbHQgc2luZCB1bmQgbcO2Z2xpY2hzdCBpbiBkZXIgZ2xlaWNoZW4gUmVnaW9uIHdvaG5lbg0KICBzaW1pbGFyX25vbl9idXllcnNfdGVtcCA8LSBub25fYnV5ZXJzICU+JQ0KICAgIGZpbHRlcihnZW5kZXIgPT0gY3VycmVudF9idXllciRnZW5kZXIsDQogICAgICAgICAgIGFicyhhZ2UgLSBjdXJyZW50X2J1eWVyJGFnZSkgPD0gNSwNCiAgICAgICAgICAgcmVnaW9uID09IGN1cnJlbnRfYnV5ZXIkcmVnaW9uKQ0KICANCiAgIyBXw6RobGUgZGVuIGFtIGJlc3RlbiBwYXNzZW5kZW4gS3VuZGVuIGF1cyAic2ltaWxhcl9ub25fYnV5ZXJzX3RlbXAiIGF1cw0KICBiZXN0X21hdGNoX2luZGV4IDwtIHdoaWNoLm1pbihhYnMoc2ltaWxhcl9ub25fYnV5ZXJzX3RlbXAkYWdlIC0gY3VycmVudF9idXllciRhZ2UpKQ0KICBiZXN0X21hdGNoIDwtIHNpbWlsYXJfbm9uX2J1eWVyc190ZW1wW2Jlc3RfbWF0Y2hfaW5kZXgsIF0NCiAgYmVzdF9tYXRjaCRpc3N1ZWQgPC0gY3VycmVudF9idXllciRpc3N1ZWQNCiAgDQogICMgZGFtaXQgbmljaHQgZGVyIGdsZWljaGUgbm9uX2J1eWVyIGRvcHBlbHQgdmVyd2VuZGV0IHdpcmQNCiAgbm9uX2J1eWVycyA8LSBub25fYnV5ZXJzICU+JSBmaWx0ZXIoY2xpZW50X2lkICE9IGJlc3RfbWF0Y2gkY2xpZW50X2lkKQ0KICANCiAgDQogIHNpbWlsYXJfbm9uX2J1eWVycyA8LSByYmluZChzaW1pbGFyX25vbl9idXllcnMsIGJlc3RfbWF0Y2gpDQogIA0KfQ0KYGBgDQoNCg0KIyMgWnVzYW1tZW5mYXNzZW4gZGVyIFRyYW5zYWt0aW9uZW4gZsO8ciBub24gYnV5ZXJzDQoNCkF1Y2ggaGllciBzb2xsZW4gZGllIFRyYW5zYWt0aW9uZW4gZ2xlaWNoIHdpZSBiZWkgZGVuIEvDpHVmZXJuIHp1c2FtbWVuZ2VmYXNzdCB3ZXJkZW4uDQoNCmBgYHtyfQ0KYWNjb3VudF9pZHMgPC0gc2ltaWxhcl9ub25fYnV5ZXJzJGFjY291bnRfaWQNCm5vbl9idXllcl90cmFuc2FjdGlvbnMgPC0gdHJhbnNhY3Rpb25zW3RyYW5zYWN0aW9ucyRhY2NvdW50X2lkICVpbiUgYWNjb3VudF9pZHMsXQ0KDQpub25fYnV5ZXJfdHJhbnNhY3Rpb25zIDwtIG1lcmdlKG5vbl9idXllcl90cmFuc2FjdGlvbnMsIHNpbWlsYXJfbm9uX2J1eWVyc1ssIGMoImFjY291bnRfaWQiLCAiaXNzdWVkIildLCBieT0iYWNjb3VudF9pZCIpDQoNCmBgYA0KYGBge3J9DQpmaWx0ZXJlZF9kZiA8LSBub25fYnV5ZXJfdHJhbnNhY3Rpb25zICU+JQ0KICBmaWx0ZXIoZGF0ZSA+PSBhcy5EYXRlKHBhc3RlMChmb3JtYXQoaXNzdWVkIC0gbW9udGhzKDEzKSwgIiVZLSVtIiksICItMDEiKSkgJg0KICAgICAgICAgZGF0ZSA8PSBhcy5EYXRlKHBhc3RlMChmb3JtYXQoaXNzdWVkIC0gbW9udGhzKDEpLCAiJVktJW0iKSwgIi0wMSIpKSAtIDEpDQpgYGANCmBgYHtyfQ0Kc3VtbWFyeV9kZiA8LSBmaWx0ZXJlZF9kZiAlPiUNCiAgZ3JvdXBfYnkoYWNjb3VudF9pZCwgbW9udGggPSBmb3JtYXQoZGF0ZSwgIiVZLSVtIikpICU+JQ0KICBzdW1tYXJpc2UoDQogICAgbWF4X2RpZmZlcmVuY2UgPSBtYXgoZGlmZmVyZW5jZSksDQogICAgbWluX2RpZmZlcmVuY2UgPSBtaW4oZGlmZmVyZW5jZSksDQogICAgbWF4X2JhbGFuY2UgPSBtYXgoYmFsYW5jZSksDQogICAgbWluX2JhbGFuY2UgPSBtaW4oYmFsYW5jZSksDQogICAgaW5pdGlhbF9iYWxhbmNlID0gZmlyc3QoYmFsYW5jZSksDQogICAgZW5kX2JhbGFuY2UgPSBsYXN0KGJhbGFuY2UpLA0KICAgIG1lYW5fYmFsYW5jZSA9IG1lYW4oYmFsYW5jZSksDQogICAgbWVkaWFuX2JhbGFuY2UgPSBtZWRpYW4oYmFsYW5jZSksDQogICAgc3RkX2JhbGFuY2UgPSBzZChiYWxhbmNlKSwNCiAgICBtZWFuX2RpZmZlcmVuY2UgPSBtZWFuKGRpZmZlcmVuY2UpLA0KICAgIG1lZGlhbl9kaWZmZXJlbmNlID0gbWVkaWFuKGRpZmZlcmVuY2UpLA0KICAgIHN0ZF9kaWZmZXJlbmNlID0gc2QoZGlmZmVyZW5jZSksDQogICAgY291bnRfcG9zaXRpdmVfZGlmZmVyZW5jZSA9IHN1bShkaWZmZXJlbmNlID4gMCksDQogICAgY291bnRfbmVnYXRpdmVfZGlmZmVyZW5jZSA9IHN1bShkaWZmZXJlbmNlIDwgMCkNCiAgKQ0Kc3VtbWFyeV9kZiA8LSBzdW1tYXJ5X2RmICU+JQ0KICBhcnJhbmdlKGFjY291bnRfaWQpDQpgYGANCg0KYGBge3J9DQojIEtvbnRyb2xsZSwgb2IgZsO8ciBqZWRlbiBhY2NvdW50X2lkIDEyIG1vbmF0ZSB2b3JoYW5kZW4gc2luZA0KbW9udGhfY291bnRzIDwtIHN1bW1hcnlfZGYgJT4lDQogIGdyb3VwX2J5KGFjY291bnRfaWQpICU+JQ0KICBzdW1tYXJpc2UobW9udGhfY291bnQgPSBuX2Rpc3RpbmN0KG1vbnRoKSkNCg0KIyBQcsO8ZmUsIG9iIGplZGVzIGFjY291bnRfaWQgMTIgTW9uYXRlIGhhdA0KbW9udGhfY291bnRzIDwtIG1vbnRoX2NvdW50cyAlPiUgZmlsdGVyKG1vbnRoX2NvdW50ICE9IDEyKQ0KbW9udGhfY291bnRzDQoNCmBgYA0KQXVjaCBoaWVyIGhhYmVuIHdpZWRlciBlaW5pZ2UgS3VuZGVuIHdlbmlnZXIgYWxzIDEyIGtvbnRpbnVpZXJsaWNoZSBNb25hdGUuDQoNCmBgYHtyfQ0Kc3VtbWFyeV9kZiA8LSBzdWJzZXQoc3VtbWFyeV9kZiwgIWFjY291bnRfaWQgJWluJSBtb250aF9jb3VudHMkYWNjb3VudF9pZCkNCmBgYA0KDQoNCmBgYHtyfQ0Kc3VtbWFyeV9kZiA8LSBzdW1tYXJ5X2RmW29yZGVyKHN1bW1hcnlfZGYkYWNjb3VudF9pZCwgcmV2KHN1bW1hcnlfZGYkbW9udGgpKSxdDQpzdW1tYXJ5X2RmJGdyb3VwX2lkIDwtIGF2ZShzZXFfYWxvbmcoc3VtbWFyeV9kZiRhY2NvdW50X2lkKSwgc3VtbWFyeV9kZiRhY2NvdW50X2lkLCBGVU4gPSBmdW5jdGlvbih4KSB7eH0pDQoNCnN1bW1hcnlfZGYkbW9udGhfbnVtYmVyIDwtIDEyDQoNCmZvciAoaSBpbiAyOm5yb3coc3VtbWFyeV9kZikpIHsNCiAgaWYgKHN1bW1hcnlfZGYkYWNjb3VudF9pZFtpXSAhPSBzdW1tYXJ5X2RmJGFjY291bnRfaWRbaS0xXSkgew0KICAgIHN1bW1hcnlfZGYkbW9udGhfbnVtYmVyW2ldIDwtIDEyDQogIH0gZWxzZSB7DQogICAgc3VtbWFyeV9kZiRtb250aF9udW1iZXJbaV0gPC0gc3VtbWFyeV9kZiRtb250aF9udW1iZXJbaS0xXSAtIDENCiAgfQ0KfQ0KDQojIEVudGZlcm5lIGRpZSBTcGFsdGUgZ3JvdXBfaWQNCnN1bW1hcnlfZGYkZ3JvdXBfaWQgPC0gTlVMTA0Kc3VtbWFyeV9kZiRtb250aCA8LSBOVUxMDQpgYGANCg0KYGBge3J9DQpzdW1tYXJ5X2RmX25vbl9idXllcnMgPC0gc3VtbWFyeV9kZiAlPiUNCiAgZ3JvdXBfYnkoYWNjb3VudF9pZCkgJT4lDQogIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSBtb250aF9udW1iZXIsDQogICAgICAgICAgICAgIHZhbHVlc19mcm9tID0gYyhtYXhfZGlmZmVyZW5jZSwgbWluX2RpZmZlcmVuY2UsIG1heF9iYWxhbmNlLCBtaW5fYmFsYW5jZSwgaW5pdGlhbF9iYWxhbmNlLCBlbmRfYmFsYW5jZSwgbWVhbl9iYWxhbmNlLCBtZWRpYW5fYmFsYW5jZSwgc3RkX2JhbGFuY2UsIG1lZGlhbl9iYWxhbmNlLCBzdGRfYmFsYW5jZSwgbWVhbl9kaWZmZXJlbmNlLCBtZWRpYW5fZGlmZmVyZW5jZSwgc3RkX2RpZmZlcmVuY2UsIGNvdW50X3Bvc2l0aXZlX2RpZmZlcmVuY2UsIGNvdW50X25lZ2F0aXZlX2RpZmZlcmVuY2UpKQ0KYGBgDQoNCkRpZSBUcmFuc2FrdGlvbnNkYXRlbiB3ZXJkZW4gbWl0IGRlbiBhbmRlcmVuIERhdGVuIHp1c2FtbWVuZ2Vmw7xndCwgdW0gcHJvIEt1bmRlIGVpbmUgWmVpbGUgaW4gZWluZW0gRGF0YWZyYW1lIHp1IGhhYmVuLg0KDQpgYGB7cn0NCg0Kc3VtbWFyeV9kZl9ub25fYnV5ZXJzIDwtIG1lcmdlKHN1bW1hcnlfZGZfbm9uX2J1eWVycywgc2ltaWxhcl9ub25fYnV5ZXJzLCBieSA9ICJhY2NvdW50X2lkIikNCmBgYA0KDQpgYGB7cn0NCm1lcmdlKHN1bW1hcnlfZGZfbm9uX2J1eWVycywgbm9uX2J1eWVycywgYnkgPSAiYWNjb3VudF9pZCIpDQpgYGANCg0KDQpgYGB7cn0NCmZpbmFsX2RmIDwtIHJiaW5kKHN1bW1hcnlfZGZfYnV5ZXJzLCBzdW1tYXJ5X2RmX25vbl9idXllcnMpDQpgYGANCg0KSmV0enQgbXVzcyBub2NoIGRhc3MgaXNzdWVkLURhdHVtIHNvd2llIHdlaXRlcmUgVmFyaWFiZWxuIGVudGZlcm50IHdlcmRlbi4NCg0KYGBge3J9DQojIEVudGZlcm5lIHdlaXRlcmUgdW5uw7Z0aWdlIFZhcmlhYmVsbiB3aWUgSUQncyBvZGVyIFdlcnRlLCB3ZWxjaGUgw7xiZXJhbGwgZ2xlaWNoIHNpbmQNCmZpbmFsX2RmIDwtIGZpbmFsX2RmICU+JSBzZWxlY3QoLWNsaWVudF9pZCwgLWRpc3RyaWN0X2lkLCAtZGlzdHJpY3RfaWQuYWNjb3VudHMsIC1kaXNwX2lkLCAtdHlwZSwgLWxvYW5faWQsIC1hY2NvdW50X2lkLCAtaXNzdWVkKQ0KYGBgDQoNCkF1c3NlcmRlbSBzY2hlaW5lbiBlaW5pZ2UgVmFyaWFiZWxuIGFscyBGYWt0b3JlbiBpbSBEYXRlbnNhdHogenUgc2Vpbiwgd2VsY2hlIGVpZ2VudGxpY2ggbnVtZXJpc2NoIHfDpHJlbi4NCg0KYGBge3J9DQpmaW5hbF9kZiR1bmVtcGxveW1lbnRfcmF0ZV85NSA8LSBhcy5udW1lcmljKGZpbmFsX2RmJHVuZW1wbG95bWVudF9yYXRlXzk1KQ0KZmluYWxfZGYkdW5lbXBsb3ltZW50X3JhdGVfOTUuYWNjb3VudHMgPC0gYXMubnVtZXJpYyhmaW5hbF9kZiR1bmVtcGxveW1lbnRfcmF0ZV85NS5hY2NvdW50cykNCmZpbmFsX2RmJGNyaW1lc185NSA8LSBhcy5udW1lcmljKGZpbmFsX2RmJGNyaW1lc185NSkNCmZpbmFsX2RmJGNyaW1lc185NS5hY2NvdW50cyA8LSBhcy5udW1lcmljKGZpbmFsX2RmJGNyaW1lc185NS5hY2NvdW50cykNCmBgYA0KDQoNCiMgTW9kZWxsZQ0KDQpBbHMgbsOkY2hzdGVzIHNvbGxlbiBNb2RlbGxlIHRyYWluaWVydCB1bmQgZXZhbHVpZXJ0IHdlcmRlbi4gVW0gZGllIFJlc3VsdGF0ZSB6dSByZXByb2R1emllcmVuLCB3aXJkIGhpZXIgZWluIGluaXRpYWxpZXIgc2VlZCBnZXNldHp0Lg0KDQpgYGB7cn0NCnNldC5zZWVkKDI3KQ0KYGBgDQoNCg0KDQoNCiMjIEZ1bmt0aW9uZW4genVyIEV2YWx1aWVydW5nIHZvbiBNb2RlbGxlbg0KDQojIyMgVHJhaW4tVGVzdC1TcGxpdA0KDQpBbHMgVm9yYmVyZWl0dW5nIGbDvHIgZGllIE1vZGVsbGUgbcO8c3NlbiB3aXIgdW5zZXJlIERhdGVuIHp1IFRyYWluaW5ncy0gdW5kIFRlc3RkYXRlbiB1bnRlcnRlaWxlbi4gRGFmw7xyIGVyc3RlbGxlbiB3aXIgZWluZSBGdW5rdGlvbiwgd2VsY2hlIGF1ZiB2ZXJzY2hpZWRlbmVuIFZhcmlhbnRlbiBkZXMgRGF0ZW5zYXR6IGdlYnJhdWNodCB3ZXJkZW4ga2Fubi4NCg0KYGBge3J9DQpzcGxpdF9kYXRhIDwtIGZ1bmN0aW9uKGRmLCB0ZXN0X3NpemUgPSAwLjIpIHsNCiAgc3BsaXQgPC0gY3JlYXRlRGF0YVBhcnRpdGlvbihkZiRoYXNfY2FyZCwgcCA9IDEgLSB0ZXN0X3NpemUsIGxpc3QgPSBGQUxTRSkNCiAgdHJhaW4gPC0gZGZbc3BsaXQsIF0NCiAgdGVzdCA8LSBkZlstc3BsaXQsIF0NCiAgDQogIHJldHVybihsaXN0KHRyYWluID0gdHJhaW4sIHRlc3QgPSB0ZXN0KSkNCn0NCmBgYA0KDQojIyMgTWV0cmlrZW4NCg0KRGFtaXQgd2lyIGRpZSB2ZXJzY2hpZWRlbmVuIE1vZGVsbGUgYmVzc2VyIGV2YWx1aWVyZW4ga8O2bm5lbiwgbcO8c3NlbiB3aXIgZGllIGdsZWljaGVuIEtlbm56YWhsZW4gdW5kIEF1c3dlcnR1bmdlbiBwcm8gTW9kZWxsIG1hY2hlbi4gWnUgZGllc2VtIFp3ZWNrIGRlZmluaWVyZW4gd2lyIGVpbmlnZSBGdW5rdGlvbmVuLCBkYW1pdCB3aXIgd2VuaWdlciByZWR1bmRhbnRlbiBDb2RlIGhhYmVuIHVuZCB1bnNlcmUgRXJnZWJuaXNzZSBpbiBlaW5lbSBlaW5oZWl0bGljaGUsIHZlcmdsZWljaGJhcmVuIEZvcm1hdCBkYWhlcmtvbW1lbi4gDQoNCkRhZsO8ciBlcnN0ZWxsZW4gd2lyIGVpbmUgRnVua3Rpb24sIGRpZSBkaWUgR2VuYXVpZ2tlaXQgKEFjY3VyYWN5KSwgQ29oZW4ncyBLYXBwYSwgTWF0dGhld3MgS29ycmVsYXRpb24sIFByw6R6aXNpb24gKFByZWNpc2lvbiksIEVyaW5uZXJ1bmcgKFJlY2FsbCkgdW5kIGRlbiBGMS1TY29yZSBmw7xyIGVpbmUgUmVpaGUgdm9uIFZvcmhlcnNhZ2VuIHVuZCBkZXJlbiBlbnRzcHJlY2hlbmRlbiB3YWhyZW4gV2VydGUgYmVyZWNobmV0Lg0KDQpXYXMgYmVkZXV0ZW4gZGllc2UgS2VubnphaGxlbiBnZW5hdT8NCg0KQWNjdXJhY3kgKEdlbmF1aWdrZWl0KTogRGllIEFjY3VyYWN5IGlzdCBkZXIgUHJvemVudHNhdHogZGVyIFZvcmhlcnNhZ2VuLCBkaWUgbWl0IGRlbiB0YXRzw6RjaGxpY2hlbiBXZXJ0ZW4gw7xiZXJlaW5zdGltbWVuLiBTaWUgd2lyZCBiZXJlY2huZXQgYWxzIEFuemFobCBkZXIga29ycmVrdGVuIFZvcmhlcnNhZ2VuIGdldGVpbHQgZHVyY2ggZGllIEdlc2FtdHphaGwgZGVyIFZvcmhlcnNhZ2VuLg0KDQpDb2hlbidzIEthcHBhIChDb2hlbidzIEthcHBhKTogQ29oZW4ncyBLYXBwYSBpc3QgZWluZSBNZXNzZ3LDtsOfZSBmw7xyIGRpZSBRdWFsaXTDpHQgdm9uIGJpbsOkcmVuIEtsYXNzaWZpa2F0aW9uZW4uIEVzIHdpcmQgdmVyd2VuZGV0LCB1bSBkaWUgw5xiZXJlaW5zdGltbXVuZyB6d2lzY2hlbiB6d2VpIEtsYXNzaWZpa2F0b3JlbiB6dSBtZXNzZW4sIGluZGVtIGVzIGRpZSDDnGJlcmVpbnN0aW1tdW5nIMO8YmVyIGRlciBlcndhcnRldGVuIMOcYmVyZWluc3RpbW11bmcgZHVyY2ggWnVmYWxsIGJlcmVjaG5ldC4gRWluIEthcHBhLVdlcnQgdm9uIDEgYmVkZXV0ZXQgcGVyZmVrdGUgw5xiZXJlaW5zdGltbXVuZywgZWluIFdlcnQgdm9uIDAgYmVkZXV0ZXQga2VpbmUgw5xiZXJlaW5zdGltbXVuZywgZGllIGJlc3NlciBpc3QgYWxzIFp1ZmFsbCwgdW5kIGVpbiBXZXJ0IHZvbiAtMSBiZWRldXRldCBrb21wbGV0dCBmYWxzY2hlIEtsYXNzaWZpa2F0aW9uZW4uDQoNCk1hdHRoZXdzIGNvcnJlbGF0aW9uIGNvZWZmaWNpZW50IChNYXR0aGV3cyBLb3JyZWxhdGlvbik6IERlciBNYXR0aGV3cyBLb3JyZWxhdGlvbiBDb2VmZmljaWVudCAoTUNDKSBpc3QgZWluZSBNZXNzZ3LDtsOfZSBmw7xyIGRpZSBRdWFsaXTDpHQgdm9uIGJpbsOkcmVuIEtsYXNzaWZpa2F0aW9uZW4uIEVyIHJlaWNodCB2b24gLTEgYmlzIDEsIHdvYmVpIGVpbiBXZXJ0IHZvbiAxIHBlcmZla3RlIEtsYXNzaWZpa2F0aW9uIGJlZGV1dGV0LCBlaW4gV2VydCB2b24gMCBlaW5lIEtsYXNzaWZpa2F0aW9uLCBkaWUgbmljaHQgYmVzc2VyIGFscyBadWZhbGwgaXN0LCB1bmQgZWluIFdlcnQgdm9uIC0xIGVpbmUga29tcGxldHQgZmFsc2NoZSBLbGFzc2lmaWthdGlvbiBiZWRldXRldC4NCg0KUHJlY2lzaW9uIChQcsOkemlzaW9uKTogRGllIFByw6R6aXNpb24gaXN0IGRlciBQcm96ZW50c2F0eiBkZXIgVm9yaGVyc2FnZW4sIGRpZSB0YXRzw6RjaGxpY2gga29ycmVrdCB3YXJlbiwgdW50ZXIgZGVyIEFubmFobWUsIGRhc3MgYWxsZSBWb3JoZXJzYWdlbiBrb3JyZWt0IHNpbmQuIFNpZSB3aXJkIGJlcmVjaG5ldCBhbHMgQW56YWhsIGRlciBrb3JyZWt0ZW4gVm9yaGVyc2FnZW4gZsO8ciBkaWUgcG9zaXRpdmUgS2xhc3NlIGdldGVpbHQgZHVyY2ggZGllIEdlc2FtdHphaGwgZGVyIFZvcmhlcnNhZ2VuIGbDvHIgZGllIHBvc2l0aXZlIEtsYXNzZS4NCg0KUmVjYWxsIChFcmlubmVydW5nKTogRGVyIFJlY2FsbCBpc3QgZGVyIFByb3plbnRzYXR6IGRlciB0YXRzw6RjaGxpY2ggcG9zaXRpdmVuIFdlcnRlLCBkaWUga29ycmVrdCB2b3JoZXJnZXNhZ3Qgd3VyZGVuLiBFciB3aXJkIGJlcmVjaG5ldCBhbHMgQW56YWhsIGRlciBrb3JyZWt0ZW4gVm9yaGVyc2FnZW4gZsO8ciBkaWUgcG9zaXRpdmUgS2xhc3NlIGdldGVpbHQgZHVyY2ggZGllIEdlc2FtdHphaGwgZGVyIHRhdHPDpGNobGljaCBwb3NpdGl2ZW4gV2VydGUuDQoNCkYxIHNjb3JlIChGMS1XZXJ0KTogRGVyIEYxLVdlcnQgaXN0IGVpbiBNYcOfIGbDvHIgZGllIFF1YWxpdMOkdCB2b24gYmluw6RyZW4gS2xhc3NpZmlrYXRpb25lbiwgZGFzIGRpZSBIYXJtb25pZXNjaGUgTWlzY2h1bmcgdm9uIFByw6R6aXNpb24gdW5kIFJlY2FsbCBkYXJzdGVsbHQuIEVzIHdpcmQgYmVyZWNobmV0IGFscyBkZXIgSGFybW9uaWVzY2hlIE1pdHRlbHdlcnQgdm9uIFByw6R6aXNpb24gdW5kIFJlY2FsbC4gRWluIGhvaGVyIEYxLVdlcnQgYmVkZXV0ZXQsIGRhc3Mgc293b2hsIFByw6R6aXNpb24gYWxzIGF1Y2ggUmVjYWxsIGhvY2ggc2luZC4NCg0KYGBge3J9DQpnZXRfbWV0cmljcyA8LSBmdW5jdGlvbihwcmVkaWN0aW9ucywgdHJ1ZV92YWx1ZXMpIHsNCiAgIyBDYWxjdWxhdGUgYWNjdXJhY3kNCiAgYWNjdXJhY3kgPC0gc3VtKHByZWRpY3Rpb25zID09IHRydWVfdmFsdWVzKSAvIGxlbmd0aChwcmVkaWN0aW9ucykNCiAgDQogICMgQ2FsY3VsYXRlIENvaGVuJ3Mga2FwcGENCiAgbiA8LSBsZW5ndGgocHJlZGljdGlvbnMpDQpvYnNlcnZlZF9hZ3JlZW1lbnQgPC0gc3VtKHByZWRpY3Rpb25zID09IHRydWVfdmFsdWVzKQ0KZXhwZWN0ZWRfYWdyZWVtZW50IDwtIHN1bShwcmVkaWN0aW9ucyA9PSB0cnVlX3ZhbHVlcykgLyBuDQprYXBwYSA8LSAob2JzZXJ2ZWRfYWdyZWVtZW50IC0gZXhwZWN0ZWRfYWdyZWVtZW50KSAvIChuIC0gZXhwZWN0ZWRfYWdyZWVtZW50KQ0KICANCiAgIyBDYWxjdWxhdGUgTWF0dGhld3MgY29ycmVsYXRpb24gY29lZmZpY2llbnQNCiAgY29uZnVzaW9uX21hdHJpeCA8LSB0YWJsZShwcmVkaWN0aW9ucywgdHJ1ZV92YWx1ZXMpDQogIHRwIDwtIGNvbmZ1c2lvbl9tYXRyaXhbMiwyXQ0KICB0biA8LSBjb25mdXNpb25fbWF0cml4WzEsMV0NCiAgZnAgPC0gY29uZnVzaW9uX21hdHJpeFsyLDFdDQogIGZuIDwtIGNvbmZ1c2lvbl9tYXRyaXhbMSwyXQ0KICBtYXR0aGV3cyA8LSAodHAgKiB0biAtIGZwICogZm4pIC8gc3FydCgodHAgKyBmcCkgKiAodHAgKyBmbikgKiAodG4gKyBmcCkgKiAodG4gKyBmbikpDQogIA0KICAjIENhbGN1bGF0ZSBwcmVjaXNpb24gYW5kIHJlY2FsbA0KICBwcmVjaXNpb24gPC0gY29uZnVzaW9uX21hdHJpeFsyLDJdIC8gc3VtKGNvbmZ1c2lvbl9tYXRyaXhbMixdKQ0KICByZWNhbGwgPC0gY29uZnVzaW9uX21hdHJpeFsyLDJdIC8gc3VtKGNvbmZ1c2lvbl9tYXRyaXhbLDJdKQ0KICANCiAgIyBDYWxjdWxhdGUgRjEgc2NvcmUNCiAgZjEgPC0gMiAqIChwcmVjaXNpb24gKiByZWNhbGwpIC8gKHByZWNpc2lvbiArIHJlY2FsbCkNCiAgDQogICMgQ3JlYXRlIGEgZGF0YSBmcmFtZSBvZiB0aGUgbWV0cmljcw0KICBtZXRyaWNzIDwtIGRhdGEuZnJhbWUoYWNjdXJhY3kgPSBhY2N1cmFjeSwga2FwcGEgPSBrYXBwYSwgbWF0dGhld3MgPSBtYXR0aGV3cywNCiAgICAgICAgICAgICAgICAgICAgICAgIHByZWNpc2lvbiA9IHByZWNpc2lvbiwgcmVjYWxsID0gcmVjYWxsLCBmMSA9IGYxKQ0KICANCiAgIyBSZXR1cm4gdGhlIGRhdGEgZnJhbWUNCiAgcmV0dXJuKG1ldHJpY3MpDQp9DQpgYGANCg0KIyMjIEtvbmZ1c2lvbnNtYXRyaXggbWl0IFBsb3QNCg0KRGllc2UgRnVua3Rpb24gZXJzdGVsbHQgZWluZSBLb25mdXNpb25zbWF0cml4IHVuZCBnaWJ0IHNpZSBhbHMgUGxvdCB6dXLDvGNrLiANCg0KRWluZSBLb25mdXNpb25zbWF0cml4IGlzdCBlaW4gd2ljaHRpZ2VzIFdlcmt6ZXVnIHp1ciBFdmFsdWF0aW9uIHZvbiBLbGFzc2lmaWthdGlvbnNtb2RlbGxlbi4gU2llIHplaWd0IGFuLCB3aWUgZ3V0IGRhcyBNb2RlbGwgaW4gZGVyIExhZ2UgaXN0LCBkaWUgdmVyc2NoaWVkZW5lbiBLbGFzc2VuIHJpY2h0aWcgenUgaWRlbnRpZml6aWVyZW4uIEluIGVpbmVyIEtvbmZ1c2lvbnNtYXRyaXggd2VyZGVuIGRpZSB0YXRzw6RjaGxpY2hlbiB1bmQgZGllIHZvbiBkZW0gTW9kZWxsIHZvcmhlcmdlc2FndGVuIEtsYXNzZW4gZ2VnZW7DvGJlcmdlc3RlbGx0LiBEaWUgTWF0cml4IGlzdCBpbiB2aWVyIFF1YWRyYW50ZW4gdW50ZXJ0ZWlsdDogdHJ1ZSBwb3NpdGl2ZXMgKFRQKSwgdHJ1ZSBuZWdhdGl2ZXMgKFROKSwgZmFsc2UgcG9zaXRpdmVzIChGUCkgdW5kIGZhbHNlIG5lZ2F0aXZlcyAoRk4pLiBUUCBzaW5kIGRpZSBGw6RsbGUsIGluIGRlbmVuIGRhcyBNb2RlbGwgZGllIEtsYXNzZSByaWNodGlnIHZvcmhlcmdlc2FndCBoYXQsIFROIHNpbmQgZGllIEbDpGxsZSwgaW4gZGVuZW4gZGFzIE1vZGVsbCBkaWUgS2xhc3NlIHJpY2h0aWcgdm9yaGVyZ2VzYWd0IGhhdCB1bmQgZGllc2UgS2xhc3NlIGF1Y2ggdGF0c8OkY2hsaWNoIHZvcmxpZWd0LCBGUCBzaW5kIGRpZSBGw6RsbGUsIGluIGRlbmVuIGRhcyBNb2RlbGwgZWluZSBLbGFzc2Ugdm9yaGVyZ2VzYWd0IGhhdCwgZGllIGluIFdpcmtsaWNoa2VpdCBuaWNodCB2b3JsaWVndCwgdW5kIEZOIHNpbmQgZGllIEbDpGxsZSwgaW4gZGVuZW4gZGFzIE1vZGVsbCBlaW5lIEtsYXNzZSBuaWNodCB2b3JoZXJnZXNhZ3QgaGF0LCBkaWUgaW4gV2lya2xpY2hrZWl0IHZvcmxpZWd0LiBFaW5lIEtvbmZ1c2lvbnNtYXRyaXggaXN0IGhpbGZyZWljaCwgdW0gZGllIEdlbmF1aWdrZWl0LCBTZW5zaXRpdml0w6R0IHVuZCBTcGV6aWZpdMOkdCBkZXMgTW9kZWxscyB6dSBiZXJlY2huZW4gdW5kIHVtIHp1IHNlaGVuLCBhbiB3ZWxjaGVuIFN0ZWxsZW4gZGFzIE1vZGVsbCBTY2h3w6RjaGVuIGhhdC4gU2llIGthbm4gYXVjaCB2ZXJ3ZW5kZXQgd2VyZGVuLCB1bSBkaWUgTGVpc3R1bmcgdm9uIHZlcnNjaGllZGVuZW4gTW9kZWxsZW4gbWl0ZWluYW5kZXIgenUgdmVyZ2xlaWNoZW4uDQoNCmBgYHtyfQ0KcGxvdF9jb25mdXNpb25fbWF0cml4IDwtIGZ1bmN0aW9uKHByZWRpY3Rpb25zLCB0cnVlX3ZhbHVlcykgew0KICAjIEVyc3RlbGxlIGVpbmUgQ29uZnVzaW9uIE1hdHJpeCBhbHMgRGF0YSBGcmFtZQ0KICBjb25mdXNpb25fbWF0cml4X2RmIDwtIGRhdGEuZnJhbWUocHJlZGljdGlvbnMsIHRydWVfdmFsdWVzKQ0KICANCiAgIyBaw6RobGUgZGllIEjDpHVmaWdrZWl0ZW4gamVkZXIgS29tYmluYXRpb24gdm9uIFZvcmhlcnNhZ2UtIHVuZCBUcnVlLVdlcnRlbg0KICBjb3VudHNfZGYgPC0gY291bnQoY29uZnVzaW9uX21hdHJpeF9kZiwgcHJlZGljdGlvbnMsIHRydWVfdmFsdWVzKQ0KICANCiAgIyBFcnN0ZWxsZSBlaW5lbiBnZ3Bsb3QtUGxvdA0KICBnZ3Bsb3QoZGF0YSA9IGNvdW50c19kZiwgYWVzKHggPSBwcmVkaWN0aW9ucywgeSA9IHRydWVfdmFsdWVzKSkgKw0KICAgIGdlb21fdGlsZShhZXMoZmlsbCA9IG4pKSArDQogICAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IG4pKSArDQogICAgc2NhbGVfZmlsbF9ncmFkaWVudChsb3cgPSAid2hpdGUiLCBoaWdoID0gImRhcmtncmVlbiIpICsNCiAgICBsYWJzKHggPSAiUHJlZGljdGVkIENsYXNzIiwgeSA9ICJUcnVlIENsYXNzIiwgdGl0bGUgPSAiQ29uZnVzaW9uIE1hdHJpeCIpDQp9DQpgYGANCg0KIyMjIFJPQyAvIEFVQw0KDQpEaWVzZSBGdW5rdGlvbiB6ZWljaG5ldCBkaWUgUk9DLUt1cnZlIHVuZCBiZXJlY2huZXQgZGllIEFyZWEgdW5kIEN1cnZlLiANCg0KRGllIFJPQy1LdXJ2ZSAoUmVjZWl2ZXIgT3BlcmF0aW5nIENoYXJhY3RlcmlzdGljIGN1cnZlKSBpc3QgZWluIHdpY2h0aWdlcyBXZXJremV1ZyB6dXIgQmV3ZXJ0dW5nIHZvbiBLbGFzc2lmaWthdG9yZW4uIFNpZSB6ZWlndCBkaWUgTGVpc3R1bmcgZGVzIEtsYXNzaWZpa2F0b3JzIGJlaSB2ZXJzY2hpZWRlbmVuIFNjaHdlbGxlbndlcnRlbiBhbiwgZGllIHp1ciBVbnRlcnNjaGVpZHVuZyB6d2lzY2hlbiB6d2VpIEtsYXNzZW4gdmVyd2VuZGV0IHdlcmRlbi4gRGllIFJPQy1LdXJ2ZSBpc3QgYmVzb25kZXJzIG7DvHR6bGljaCwgd2VubiBkaWUgYmVpZGVuIEtsYXNzZW4gaW0gVmVyaMOkbHRuaXMgdW5hdXNnZWdsaWNoZW4gc2luZCwgd2llIGVzIG9mdCBkZXIgRmFsbCBpc3QsIHdlbm4gZXMgZGFydW0gZ2VodCwgc2VsdGVuZSBFcmVpZ25pc3NlIHdpZSBLcmFua2hlaXRlbiBvZGVyIEJldHJ1ZyB6dSBlcmtlbm5lbi4NCg0KRGllIFJPQy1LdXJ2ZSBpc3QgYXVmIGRlciB4LUFjaHNlIGRlciBmYWxzY2gtcG9zaXRpdi1SYXRlIChGUFIpIHVuZCBhdWYgZGVyIHktQWNoc2UgZGVyIHdhaHItcG9zaXRpdi1SYXRlIChUUFIpIGF1ZmdldHJhZ2VuLiBEZXIgRlBSIGdpYnQgYW4sIHdpZSB2aWVsZSBmYWxzY2ggcG9zaXRpdmUgRXJnZWJuaXNzZSBlcyBnaWJ0LCB3w6RocmVuZCBkZXIgVFBSIGFuZ2lidCwgd2llIHZpZWxlIHdhaHIgcG9zaXRpdmUgRXJnZWJuaXNzZSBlcnppZWx0IHdlcmRlbi4gRWluIHBlcmZla3RlciBLbGFzc2lmaWthdG9yIHfDvHJkZSBlaW5lIFJPQy1LdXJ2ZSBoYWJlbiwgZGllIGltIG9iZXJlbiBsaW5rZW4gQmVyZWljaCBiZWdpbm50IHVuZCBuYWNoIHJlY2h0cyBvYmVuIHZlcmzDpHVmdCwgd29iZWkgYWxsZSBGw6RsbGUga29ycmVrdCBrbGFzc2lmaXppZXJ0IHdlcmRlbi4gRWluIHp1ZsOkbGxpZ2VyIEtsYXNzaWZpa2F0b3Igd8O8cmRlIGVpbmUgZGlhZ29uYWwgdmVybGF1ZmVuZGUgUk9DLUt1cnZlIGhhYmVuLCBkYSBkaWUgRlBSIHVuZCBUUFIgenVmw6RsbGlnIHZlcnRlaWx0IHNpbmQuDQoNCkRpZSBBdUMgKEFyZWEgVW5kZXIgdGhlIEN1cnZlKSBpc3QgZWluZSBNZXRyaWssIGRpZSBhdXMgZGVyIFJPQy1LdXJ2ZSBiZXJlY2huZXQgd2lyZCB1bmQgZGllIExlaXN0dW5nIGRlcyBLbGFzc2lmaWthdG9ycyB6dXNhbW1lbmZhc3N0LiBTaWUgZ2lidCBhbiwgd2llIGd1dCBkZXIgS2xhc3NpZmlrYXRvciBpbSBWZXJnbGVpY2ggenUgZWluZW0genVmw6RsbGlnZW4gS2xhc3NpZmlrYXRvciBpc3QuIEVpbmUgQVVDIHZvbiAxIGJlZGV1dGV0LCBkYXNzIGRhcyBNb2RlbGwgcGVyZmVrdCBpbiBkZXIgTGFnZSBpc3QsIHBvc2l0aXZlIHVuZCBuZWdhdGl2ZSBLbGFzc2VuIHp1IHVudGVyc2NoZWlkZW4sIHfDpGhyZW5kIGVpbmUgQVVDIHZvbiAwLjUgYmVkZXV0ZXQsIGRhc3MgZGFzIE1vZGVsbCBrZWluZSBiZXNzZXJlIExlaXN0dW5nIGFscyBadWZhbGwgZXJ6aWVsdC4gRGllIEFVQyBrYW5uIFdlcnRlIHp3aXNjaGVuIDAgdW5kIDEgYW5uZWhtZW4uIEVpbmUgQVVDIHZvbiAwIGJlZGV1dGV0LCBkYXNzIGRhcyBNb2RlbGwgdsO2bGxpZyBpbmtvcnJla3QgaXN0LiBJbSBBbGxnZW1laW5lbiBnaWx0LCBqZSBncsO2w59lciBkaWUgQVVDLCBkZXN0byBiZXNzZXIgaXN0IGRhcyBNb2RlbGwgaW0gVmVyZ2xlaWNoIHp1IGFuZGVyZW4gTW9kZWxsZW4uDQoNCmBgYHtyfQ0KbWFrZV9yb2NfcGxvdF9hbmRfZ2V0X2F1YyA8LSBmdW5jdGlvbihwcmVkaWN0aW9ucywgdHJ1ZV92YWx1ZXMpew0KDQpyb2NfY3VydmUgPC0gcm9jKGFzLm51bWVyaWMocHJlZGljdGlvbnMpLCBhcy5udW1lcmljKHRydWVfdmFsdWVzKSAtIDEpDQoNCnBsb3Qocm9jX2N1cnZlLCB4bGFiID0gIkZhbHNlIFBvc2l0aXZlIFJhdGUiLCB5bGFiID0gIlRydWUgUG9zaXRpdmUgUmF0ZSIsIG1haW4gPSJST0MgQ3VydmUiKQ0KDQphdWMgPC0gYXVjKHJvY19jdXJ2ZSkNCg0KcmV0dXJuKGF1YykNCn0NCmBgYA0KDQoNCiMjIyBGZWF0dXJlIEltcG9ydGFuY2UNCg0KVW0gZGllIEZlYXR1cmUgSW1wb3J0YW5jZSBnZW5hdWVyIHp1IHVudGVyc3VjaGVuLCBkZWZpbmllcmVuIHdpciBlaW5lIEZ1bmt0aW9uLCB3ZWxjaGUgZsO8ciBlaW4gTW9kZWxsIGRpZSBGZWF0dXJlIEltcG9ydGFuY2UgYmVyZWNobmV0IHVuZCBkaWUgVG9wIDEwIEZlYXR1cmVzIGluIGVpbmVtIEJhcnBsb3QgYXVzZ2lidC5EaWUgRnVua3Rpb24gcGxvdF9mZWF0dXJlX2ltcG9ydGFuY2UoKSBuaW1tdCBhbHMgRWluZ2FiZXBhcmFtZXRlciBlaW4gbWFzY2hpbmVsbGVzIExlcm5tb2RlbGwgdW5kIHBsb3R0ZXQgZGllIEZlYXR1cmUtSW1wb3J0YW5jZSBkZXMgTW9kZWxscyBhbHMgQmFycGxvdC4gRGllIEZ1bmt0aW9uIHVudGVyc2NoZWlkZXQgZHJlaSBBcnRlbiB2b24gTW9kZWxsZW46IExvZ2lzdGlzY2hlIFJlZ3Jlc3Npb25lbiwgRW50c2NoZWlkdW5nc2LDpHVtZSB1bmQgUmFuZG9tIEZvcmVzdC4gRsO8ciBqZWRlIEFydCB2b24gTW9kZWxsIHdpcmQgZGllIEZlYXR1cmUtSW1wb3J0YW5jZSBhdWYgZWluZSBzcGV6aWZpc2NoZSBBcnQgdW5kIFdlaXNlIGJlcmVjaG5ldC4gQW5zY2hsaWXDn2VuZCB3ZXJkZW4gZGllIEZlYXR1cmUtSW1wb3J0YW5jZS1XZXJ0ZSB1bmQgZGllIE5hbWVuIGRlciBGZWF0dXJlcyBhYnN0ZWlnZW5kIHNvcnRpZXJ0LCB3b2JlaSBudXIgZGllIDEwIHdpY2h0aWdzdGVuIEZlYXR1cmVzIGJlcsO8Y2tzaWNodGlndCB3ZXJkZW4uIERlciBCYXJwbG90IHplaWd0IGRhbm4gZGllIEZlYXR1cmUtSW1wb3J0YW5jZS1XZXJ0ZSBmw7xyIGRpZSBlbnRzcHJlY2hlbmRlbiBGZWF0dXJlcyBhbi4gRGVyIFRpdGVsIGRlcyBQbG90cyBsYXV0ZXQgIkZlYXR1cmUgSW1wb3J0YW5jZSIgdW5kIGRpZSB5LUFjaHNlIGlzdCBtaXQgIkltcG9ydGFuY2UiIGJlc2NocmlmdGV0LiBEaWUgTmFtZW4gZGVyIEZlYXR1cmVzIHdlcmRlbiByb3RpZXJ0IGFuZ2V6ZWlndCwgdW0gUGxhdHogenUgc3BhcmVuLCB1bmQgZGllIEZhcmJlbiBkZXIgZWluemVsbmVuIEZlYXR1cmVzIHNpbmQgdW50ZXJzY2hpZWRsaWNoLg0KDQpgYGB7cn0NCnBsb3RfZmVhdHVyZV9pbXBvcnRhbmNlIDwtIGZ1bmN0aW9uKG1vZGVsKSB7DQogIA0KICAjIExvZ2lzdGlzY2hlIFJlZ3Jlc3Npb24NCiAgaWYgKGNsYXNzKG1vZGVsKVsxXSA9PSAiZ2xtIikgew0KICAgIGltcG9ydGFuY2UgPC0gYWJzKGNvZWYobW9kZWwpWy0xXSkNCiAgICBuYW1lcyA8LSBuYW1lcyhpbXBvcnRhbmNlKQ0KICAgIA0KICAgICMgU29ydCBmZWF0dXJlIGltcG9ydGFuY2UgYW5kIG5hbWVzIGluIGRlY3JlYXNpbmcgb3JkZXINCiAgaW1wb3J0YW5jZSA8LSBoZWFkKGltcG9ydGFuY2Vbb3JkZXIoaW1wb3J0YW5jZSwgZGVjcmVhc2luZyA9IFRSVUUpXSwgMTApDQogIG5hbWVzIDwtIG5hbWVzW29yZGVyKGltcG9ydGFuY2UsIGRlY3JlYXNpbmcgPSBUUlVFKV0NCiAgfSANCiAgDQogICMgUmFuZG9tIEZvcmVzdA0KICBlbHNlIGlmIChjbGFzcyhtb2RlbClbMV0gPT0gInJhbmRvbUZvcmVzdCIpIHsNCiAgICBpbXBvcnRhbmNlIDwtIG1vZGVsJGltcG9ydGFuY2UNCiAgICBuYW1lcyA8LSByb3cubmFtZXMoaW1wb3J0YW5jZSkNCiAgfSANCiAgDQogICMgVW5yZWNvZ25pemVkIG1vZGVsDQogIGVsc2Ugew0KICAgIHN0b3AoIlVucmVjb2duaXplZCBtb2RlbCIpDQogIH0NCiAgDQogIA0KICANCiAgIyBQbG90IGZlYXR1cmUgaW1wb3J0YW5jZQ0KICBiYXJwbG90KGltcG9ydGFuY2UsIG5hbWVzLmFyZyA9IG5hbWVzLCBsYXMgPSAyLCBjZXgubmFtZXMgPSAwLjYsDQogICAgICAgICAgbWFpbiA9ICJGZWF0dXJlIEltcG9ydGFuY2UiLCB4bGFiID0gIiIsIHlsYWIgPSAiSW1wb3J0YW5jZSIpDQp9DQpgYGANCg0KIyMjIFRocmVzaG9sZCBQbG90DQoNCmBgYHtyfQ0KDQpmaW5kX2Jlc3RfdGhyZXNob2xkIDwtIGZ1bmN0aW9uKHByZWRpY3Rpb25zLCBhY3R1YWxfdmFsdWVzKSB7DQpiZXN0X3RocmVzaG9sZCA8LSAwDQpiZXN0X2YxIDwtIDANCnRocmVzaG9sZHMgPC0gc2VxKDAuMDEsIDAuOTksIDAuMDEpDQpmMV9zY29yZXMgPC0gcmVwKDAsIGxlbmd0aCh0aHJlc2hvbGRzKSkNCg0KZm9yIChpIGluIDE6bGVuZ3RoKHRocmVzaG9sZHMpKSB7DQogICMgU2V0IHRoZSB0aHJlc2hvbGQgZm9yIHRoZSBwcmVkaWN0aW9ucw0KICBwcmVkaWN0aW9uc190aHJlc2hvbGQgPC0gaWZlbHNlKHByZWRpY3Rpb25zID4gdGhyZXNob2xkc1tpXSwgVFJVRSwgRkFMU0UpDQogIA0KICBpZiAoYWxsKHByZWRpY3Rpb25zX3RocmVzaG9sZCkpIHsNCiAgbmV4dA0KfSBlbHNlIGlmIChhbGwoIXByZWRpY3Rpb25zX3RocmVzaG9sZCkpIHsNCiAgbmV4dA0KfQ0KICANCiAgIyBDYWxjdWxhdGUgdGhlIGYxDQogIGYxIDwtIGdldF9tZXRyaWNzKHByZWRpY3Rpb25zX3RocmVzaG9sZCwgYWN0dWFsX3ZhbHVlcykkZjENCiAgDQogIGYxX3Njb3Jlc1tpXSA8LSBmMQ0KICANCiAgIyBVcGRhdGUgdGhlIGJlc3QgdGhyZXNob2xkIGFuZCBiZXN0IGYxIGlmIG5lY2Vzc2FyeQ0KICBpZiAoZjEgPiBiZXN0X2YxKSB7DQogICAgYmVzdF90aHJlc2hvbGQgPC0gdGhyZXNob2xkc1tpXQ0KICAgIGJlc3RfZjEgPC0gZjENCiAgfQ0KfQ0KDQojIERpc3BsYXkgdGhlIGJlc3QgdGhyZXNob2xkIGFuZCBiZXN0IHJlY2FsbA0KcHJpbnQocGFzdGUoIkJlc3QgdGhyZXNob2xkOiIsIGJlc3RfdGhyZXNob2xkKSkNCnByaW50KHBhc3RlKCJCZXN0IEYxOiIsIGJlc3RfZjEpKQ0KDQpwbG90KHRocmVzaG9sZHMsIGYxX3Njb3JlcywgdHlwZSA9ICJsIiwgeGxhYiA9ICJUaHJlc2hvbGQiLCB5bGFiID0gIkYxIFNjb3JlIiwgbWFpbiA9ICJGMSBTY29yZSB3aXRoIGRpZmZlcmVudCB0aHJlc2hvbGRzIikNCn0NCmBgYA0KDQojIEJhc2VsaW5lIE1vZGVsbA0KDQpBbHMgZXJzdGVzIHNvbGwgZWluZSBsb2dpc3Rpc2NoZSBSZWdyZXNzaW9uIG1pdCBkZW4gSW5mb3JtYXRpb25lbiBBbHRlciwgR2VzY2hsZWNodCwgRG9taXppbHJlZ2lvbiwgVmVybcO2Z2VuIChiYWxhbmNlLVNjaG5pdHQgw7xiZXIgYWxsZSBNb25hdGUpIHVuZCBVbXNhdHogKGRpZmZlcmVuY2UtU2Nobml0dCDDvGJlciBhbGxlIE1vbmF0ZSkgYWxzIEJhc2VsaW5lIE1vZGVsbCBlcnN0ZWxsdCB3ZXJkZW4uRGFmw7xyIG3DvHNzZW4gd2lyIGt1cnogZWluIG5ldWVzIERhdGFmcmFtZSBlcnN0ZWxsZW4uDQoNCmBgYHtyfQ0KYmFzZWxpbmVfZGF0YSA8LSBmaW5hbF9kZg0KYmFzZWxpbmVfZGF0YSRtZWFuX2JhbGFuY2UgPC0gcm93TWVhbnMoZmluYWxfZGZbLCBjKCJtZWFuX2JhbGFuY2VfMSIsICJtZWFuX2JhbGFuY2VfMiIsICJtZWFuX2JhbGFuY2VfMyIsICJtZWFuX2JhbGFuY2VfNCIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIm1lYW5fYmFsYW5jZV81IiwgIm1lYW5fYmFsYW5jZV82IiwgIm1lYW5fYmFsYW5jZV83IiwgIm1lYW5fYmFsYW5jZV84IiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAibWVhbl9iYWxhbmNlXzkiLCAibWVhbl9iYWxhbmNlXzEwIiwgIm1lYW5fYmFsYW5jZV8xMSIsICJtZWFuX2JhbGFuY2VfMTIiKV0pDQpiYXNlbGluZV9kYXRhJG1lYW5fZGlmZmVyZW5jZSA8LSByb3dNZWFucyhmaW5hbF9kZlssIGMoIm1lYW5fZGlmZmVyZW5jZV8xIiwgIm1lYW5fZGlmZmVyZW5jZV8yIiwgIm1lYW5fZGlmZmVyZW5jZV8zIiwgIm1lYW5fZGlmZmVyZW5jZV80IiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJtZWFuX2RpZmZlcmVuY2VfNSIsICJtZWFuX2RpZmZlcmVuY2VfNiIsICJtZWFuX2RpZmZlcmVuY2VfNyIsICJtZWFuX2RpZmZlcmVuY2VfOCIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAibWVhbl9kaWZmZXJlbmNlXzkiLCAibWVhbl9kaWZmZXJlbmNlXzEwIiwgIm1lYW5fZGlmZmVyZW5jZV8xMSIsICJtZWFuX2RpZmZlcmVuY2VfMTIiKV0pDQpiYXNlbGluZV9kYXRhIDwtIGJhc2VsaW5lX2RhdGFbLCBjKCJhZ2UiLCAiZ2VuZGVyIiwgInJlZ2lvbiIsICJoYXNfY2FyZCIsICJtZWFuX2JhbGFuY2UiLCAibWVhbl9kaWZmZXJlbmNlIildDQpiYXNlbGluZV9kYXRhJGhhc19jYXJkIDwtIGFzLmZhY3RvcihiYXNlbGluZV9kYXRhJGhhc19jYXJkKQ0KYGBgDQoNCkRhcmF1ZiB3aXJkIGVpbiBUcmFpbi1UZXN0LVNwbGl0IG1pdCA4MCUgVHJhaW5pbmdzZGF0ZW4gdW5kIDIwJSBUZXN0ZGF0ZW4gZ2VicmF1Y2h0Lg0KIA0KYGBge3J9DQpzcGxpdHMgPC0gc3BsaXRfZGF0YShiYXNlbGluZV9kYXRhLCB0ZXN0X3NpemUgPSAwLjIpDQp0cmFpbiA8LSBzcGxpdHMkdHJhaW4NCnRlc3QgPC0gc3BsaXRzJHRlc3QNCmBgYA0KDQpEYXMgUmVncmVzc2lvbnNtb2RlbGwgd2lyZCBhdWYgZGVuIFRyYWluaW5nc2RhdGVuIHRyYWluaWVydC4NCg0KYGBge3J9DQojIEZpdCB0aGUgbW9kZWwgb24gdGhlIHRyYWluaW5nIGRhdGENCm1vZGVsIDwtIGdsbShoYXNfY2FyZCB+IC4sIGRhdGEgPSB0cmFpbiwgZmFtaWx5ID0gYmlub21pYWwpDQpgYGANCg0KQWxzIG7DpGNoc3RlcyBzb2xsZW4gZGllIFByZWRpY3Rpb25zIGdlbWFjaHQgdW5kIGRhbWl0IGRpZSBNZXRyaWtlbiBlcnN0ZWxsdCB3ZXJkZW4uIERhIGRpZSBsb2dpc3Rpc2NoZSBSZWdyZXNzaW9uIGVpbmUgWmFobCB6d2lzY2hlbiAwIHVuZCAxIHp1csO8Y2tnaWJ0LCBtw7xzc2VuIHdpciBhbmhhbmQgZWluZXMgVGhyZXNob2xkcyBkaWUgV2VydGUgenUgVFJVRSB1bmQgRkFMU0UgdW13YW5kZWxuLiBEZXIgVGhyZXNob2xkIGdpYnQgYW4sIGFiIHdlbGNoZW0gV2FocnNjaGVpbmxpY2hrZWl0c3dlcnQgZWluZSBWb3JoZXJzYWdlIGFscyBwb3NpdGl2IGJldHJhY2h0ZXQgd2lyZC4gU3RhbmRhcmRtw6TDn2lnIGlzdCBkZXIgVGhyZXNob2xkIGF1ZiAwLjUgZ2VzZXR6dCwgd2FzIGJlZGV1dGV0LCBkYXNzIGFsbGUgV2FocnNjaGVpbmxpY2hrZWl0ZW4gZ3LDtsOfZXIgYWxzIDAuNSBhbHMgcG9zaXRpdiB1bmQgYWxsZSBXYWhyc2NoZWlubGljaGtlaXRlbiBrbGVpbmVyIGFscyAwLjUgYWxzIG5lZ2F0aXYgYmV0cmFjaHRldCB3ZXJkZW4uDQoNCmBgYHtyfQ0KIyBNYWtlIHByZWRpY3Rpb25zIG9uIHRoZSB0ZXN0IGRhdGENCnByZWRpY3Rpb25zIDwtIHByZWRpY3QobW9kZWwsIHRlc3QsIHR5cGUgPSAicmVzcG9uc2UiKQ0KDQp0aHJlc2hvbGQgPC0gMC41DQpwcmVkaWN0aW9uc190aHJlc2hvbGQgPC0gaWZlbHNlKHByZWRpY3Rpb25zID4gdGhyZXNob2xkLCBUUlVFLCBGQUxTRSkNCg0KDQpnZXRfbWV0cmljcyhwcmVkaWN0aW9uc190aHJlc2hvbGQsIHRlc3QkaGFzX2NhcmQpDQpgYGANCg0KYGBge3J9DQpwbG90X2NvbmZ1c2lvbl9tYXRyaXgocHJlZGljdGlvbnNfdGhyZXNob2xkLCB0ZXN0JGhhc19jYXJkKQ0KYGBgDQoNCmBgYHtyfQ0KbWFrZV9yb2NfcGxvdF9hbmRfZ2V0X2F1YyhwcmVkaWN0aW9uc190aHJlc2hvbGQsIHRlc3QkaGFzX2NhcmQpDQpgYGANCg0KYGBge3J9DQpwbG90X2ZlYXR1cmVfaW1wb3J0YW5jZShtb2RlbCkNCmBgYA0KDQoNCiMjIFRocmVzaG9sZCANCg0KRGVyIFRocmVzaG9sZC1XZXJ0IGvDtm5udGUgYWJlciBhbmdlcGFzc3Qgd2VyZGVuLiBKZSBuYWNoIFZlcsOkbmRlcnVuZyB3ZXJkZW4gZGllIHZlcnNjaGllZGVuZW4gTWV0cmlrZW4gYmVzc2VyIG9kZXIgc2NobGVjaHRlci4gRGllIEZyYWdlIGRhYmVpIGlzdCwgb2Igd2lyIGVoZXIgbWVociBmYWxzY2hlIFBvc2l0aXZ2b3JoZXJzYWdlbiBvZGVyIE5lZ2F0aXZ2b3JoZXJzYWdlbiB3b2xsZW4uIFdlbm4gd2lyIGRlbiBUaHJlc2hvbGQgc2Vua2VuLCB3aXJkIGJzcHcuIGRlciBSZWNhbGwgaMO2aGVyLiBXZW5uIHdpciBkZW4gVGhyZXNob2xkIGVyaMO2aGVuLCB3aXJkIGRpZSBQcmVjaXNpb24gaMO2aGVyLiBFcyBpc3QgYWJlciBuaWNodCBzaW5udm9sbCwgYW5oYW5kIGRlcyBUaHJlc2hvbGRzIGRpZXNlIGJlaWRlbiBNZXRyaWtlbiB6dSBtYXhpbWllcmVuLCBkYSBlcyBiZWkgYmVpZGVuIGZhc3Qga2VpbmUgUG9zaXRpdmVuIGJ6dy4gTmVnYXRpdmUgUHJlZGljdGlvbnMgbWVociBnaWJ0LiANCg0KRGVyIEYxIGlzdCBlaW5lIFp1c2FtbWVuZmFzc3VuZyBkZXIgUHJlY2lzaW9uIHVuZCBkZXIgUmVjYWxsLiBXaXIgdW50ZXJzdWNoZW4sIG1pdCB3ZWxjaGVtIFRocmVzaG9sZCBkZXIgaMO2Y2hzdGUgRjEtU2NvcmUgZXJ6aWVsdCB3ZXJkZW4ga2Fubi4NCg0KYGBge3J9DQpmaW5kX2Jlc3RfdGhyZXNob2xkKHByZWRpY3Rpb25zLCB0ZXN0JGhhc19jYXJkKQ0KYGBgDQoNCldpZSBzZWhlbiBkaWUgYW5kZXJlbiBNZXRyaWtlbiBtaXQgZGllc2VtIFRocmVzaG9sZCBhdXM/DQoNCmBgYHtyfQ0KcHJlZGljdGlvbnNfdGhyZXNob2xkIDwtIGlmZWxzZShwcmVkaWN0aW9ucyA+IGJlc3RfdGhyZXNob2xkLCBUUlVFLCBGQUxTRSkNCmdldF9tZXRyaWNzKHByZWRpY3Rpb25zX3RocmVzaG9sZCwgdGVzdCRoYXNfY2FyZCkNCmBgYA0KDQoNCiMgUmVncmVzc2lvbnNtb2RlbGwgbWl0IGFsbGVuIERhdGVuDQoNCk51biBzb2xsIGRpZXNlcyBCYXNlbGluZS1Nb2RlbGwgdmVyYmVzc2VydCB3ZXJkZW4uIEFscyBlcnN0ZXMgcHJvYmllcmVuIHdpciwgZGFzIGdsZWljaGUgTW9kZWxsIChMb2dpc3Rpc2NoZSBSZWdyZXNzaW9uKSBtaXQgbWVociBJbnB1dC1QYXJhbWV0ZXJuIHp1IHRyYWluaWVyZW4uIA0KDQpEYSBkaWUgbG9naXN0aXNjaGUgUmVncmVzc2lvbiBQcm9ibGVtZSBtaXQgRmFrdG9yZW4gaGF0LCB3ZWxjaGUgbnVyIGltIFRyYWluaW5ncy0gYnp3LiBUZXN0ZGF0ZW5zYXR6IHZvcmtvbW1lbiB1bmQgYXVjaCBuaWNodCBndXQgbWl0IE5BJ3MgdW1nZWhlbiBrYW5uLCBtw7xzc2VuIHdpciB6dWVyc3Qgbm9jaCBlaW5pZ2UgQW5wYXNzdW5nZW4gYW0gRGF0ZW5zYXR6IHZvcm5laG1lbi4NCg0KQWxzIGVyc3RlcyBlbnRmZXJuZW4gd2lyIGFsbGUgS29sb25uZW4sIHdlbGNoZSBGYWt0b3JlbiBzaW5kIHVuZCBtZWhyIGFscyAxMCB2ZXJzY2hpZWRlbmUgQXVzcHLDpGd1bmdlbiBoYWJlbi4NCg0KYGBge3J9DQojIEVybWl0dGxlIGRpZSBudW1lcmlzY2hlbiBNZXJrbWFsZSBpbiBkZW4gVHJhaW5pbmdzZGF0ZW4NCm51bWVyaWNfdmFycyA8LSBzYXBwbHkoZmluYWxfZGYsIGlzLm51bWVyaWMpDQoNCiMgRXJzdGVsbGUgZWluIFN1YnNldCBkZXIgVHJhaW5pbmdzZGF0ZW4gb2huZSBkaWUgbnVtZXJpc2NoZW4gTWVya21hbGUNCnRyYWluX25vX251bWVyaWMgPC0gZmluYWxfZGZbLCAhbnVtZXJpY192YXJzXQ0KDQojIEVybWl0dGxlIGRpZSBBbnphaGwgZGVyIEthdGVnb3JpZW4gZsO8ciBqZWRlcyBNZXJrbWFsDQpudW1fY2F0ZWdvcmllcyA8LSBzYXBwbHkodHJhaW5fbm9fbnVtZXJpYywgZnVuY3Rpb24oeCkgbGVuZ3RoKHVuaXF1ZSh4KSkpDQoNCiMgw5xiZXJwcsO8ZmUsIG9iIGVpbiBNZXJrbWFsIHp1IHZpZWxlIEthdGVnb3JpZW4gaGF0DQp0b29fbWFueV9jYXRlZ29yaWVzIDwtIG51bV9jYXRlZ29yaWVzID4gMTANCg0KIyBHaWIgZGllIE5hbWVuIGRlciBNZXJrbWFsZSBhdXMsIGRpZSB6dSB2aWVsZSBLYXRlZ29yaWVuIGhhYmVuDQpjb2x1bW5zX3RvX3JlbW92ZSA8LSBjb2xuYW1lcyh0cmFpbl9ub19udW1lcmljKVt0b29fbWFueV9jYXRlZ29yaWVzXQ0KDQoNCiMgRXJtaXR0bGUgZGllIFNwYWx0ZW5uYW1lbiwgZGllIGJlaGFsdGVuIHdlcmRlbiBzb2xsZW4NCmtlZXBfY29sdW1ucyA8LSBzZXRkaWZmKGNvbG5hbWVzKGZpbmFsX2RmKSwgY29sdW1uc190b19yZW1vdmUpDQoNCiMgRXJzdGVsbGUgZWluIFN1YnNldCBkZXMgRGF0YWZyYW1lcyBtaXQgZGVuIGJlaGFsdGVuZW4gU3BhbHRlbm5hbWVuDQpmaW5hbF9kZl9zaW1wbGlmaWVkIDwtIGZpbmFsX2RmWywga2VlcF9jb2x1bW5zXQ0KDQpjb2x1bW5zX3RvX3JlbW92ZQ0KYGBgDQoNCk51biBtdXNzIGRlciBEYXRlbnNhdHogbm9jaCBhdWYgTkEncyDDvGJlcnByw7xmdCB3ZXJkZW4uDQoNCmBgYHtyfQ0KIyBDb3VudCB0aGUgbnVtYmVyIG9mIE5BIHZhbHVlcyBpbiB0aGUgZGF0YSBmcmFtZQ0KbnVtX25hIDwtIHN1bShpcy5uYShmaW5hbF9kZl9zaW1wbGlmaWVkKSkNCg0KIyBQcmludCB0aGUgdG90YWwgbnVtYmVyIG9mIE5BIHZhbHVlcw0KcHJpbnQocGFzdGUoIlRvdGFsIG51bWJlciBvZiBOQSB2YWx1ZXM6IiwgbnVtX25hKSkNCg0KIyBDcmVhdGUgYSBsb2dpY2FsIHZlY3RvciBpbmRpY2F0aW5nIHdoZXRoZXIgZWFjaCBlbGVtZW50IGlzIE5BDQpuYV9tYXRyaXggPC0gaXMubmEoZmluYWxfZGZfc2ltcGxpZmllZCkNCg0KIyBTdW0gdGhlIG51bWJlciBvZiBOQSB2YWx1ZXMgcGVyIHJvdw0KbmFfY291bnRzIDwtIHJvd1N1bXMobmFfbWF0cml4KQ0KDQojIENvdW50IHRoZSByb3dzIHdpdGggTkEgdmFsdWVzDQpudW1fbmFfcm93cyA8LSBzdW0obmFfY291bnRzID4gMCkNCg0KIyBQcmludCB0aGUgbnVtYmVyIG9mIHJvd3Mgd2l0aCBOQSB2YWx1ZXMNCnByaW50KHBhc3RlKCJOdW1iZXIgb2Ygcm93cyB3aXRoIE5BIHZhbHVlczoiLCBudW1fbmFfcm93cykpDQoNCiMgU3VtIHRoZSBudW1iZXIgb2YgTkEgdmFsdWVzIHBlciBjb2x1bW4NCm5hX2NvdW50c19jb2xzIDwtIGNvbFN1bXMoKG5hX21hdHJpeCkpDQoNCiMgQ291bnQgdGhlIGNvbHVtbnMgd2l0aCBOQSB2YWx1ZXMNCm51bV9uYV9jb2xzIDwtIHN1bShuYV9jb3VudHNfY29scyA+IDApDQoNCiMgUHJpbnQgdGhlIG51bWJlciBvZiBjb2x1bW5zIHdpdGggTkEgdmFsdWVzDQpwcmludChwYXN0ZSgiTnVtYmVyIG9mIGNvbHVtbnMgd2l0aCBOQSB2YWx1ZXM6IiwgbnVtX25hX2NvbHMpKQ0KYGBgDQoNCkZhc3QgamVkZSBaZWlsZSBoYXQgaXJnZW5kd28gZWluIE5BLiBFcyBzaW5kIGF1Y2ggdmllbGUgS29sb25uZW4gYmV0cm9mZmVuLiBXaXIga8O2bm5lbiBhbHNvIG5pY2h0IGFsbGUgT2JzZXJ2YXRpb25lbiBvZGVyIFZhcmlhYmVsbiBtaXQgTkEncyBlbnRmZXJuZW4sIGRhIHNvbnN0IGRlciBEYXRlbnZlcmx1c3Qgc2VociBncm9zcyB3w6RyZS4gRGFoZXIgaW1wdXRpZXJlbiB3aXIgZGllIG51bWVyaXNjaGVuIGZlaGxlbmRlbiBXZXJ0ZSBtaXQgZGVtIE1lZGlhbiB1bmQgZGllIGZlaGxlbmRlbiBrYXRlZ29yaWFsZW4gV2VydGUgbWl0IGRlbSBXZXJ0LCB3ZWxjaGVyIGFtIG1laXN0ZW4gdm9ya29tbXQuDQoNCmBgYHtyfQ0KIyBJbXB1dGUgTkEgbnVtYmVycyB3aXRoIHRoZSBtZWRpYW4NCmZpbmFsX2RmX3NpbXBsaWZpZWQgPC0gZmluYWxfZGZfc2ltcGxpZmllZCAlPiUgDQogIG11dGF0ZV9pZihpcy5udW1lcmljLCBsaXN0KH4gaWZfZWxzZShpcy5uYSguKSwgbWVkaWFuKC4sIG5hLnJtID0gVFJVRSksIC4pKSkNCg0KIyBJbXB1dGUgTkEgc3RyaW5ncy9mYWN0b3JzIHdpdGggdGhlIG1vc3QgY29tbW9uIHZhbHVlDQpmaW5hbF9kZl9zaW1wbGlmaWVkIDwtIGZpbmFsX2RmX3NpbXBsaWZpZWQgJT4lIA0KICBtdXRhdGVfaWYoaXMuY2hhcmFjdGVyLCBsaXN0KH4gaWZfZWxzZShpcy5uYSguKSwgbW9kZSguKSwgLikpKQ0KYGBgDQoNCmBgYHtyfQ0Kc3VtKGlzLm5hKGZpbmFsX2RmX3NpbXBsaWZpZWQpKQ0KYGBgDQoNCg0KYGBge3J9DQpzcGxpdHMgPC0gc3BsaXRfZGF0YShmaW5hbF9kZl9zaW1wbGlmaWVkLCB0ZXN0X3NpemUgPSAwLjIpDQp0cmFpbiA8LSBzcGxpdHMkdHJhaW4NCnRlc3QgPC0gc3BsaXRzJHRlc3QNCmBgYA0KDQpgYGB7cn0NCiMgRml0IHRoZSBtb2RlbCBvbiB0aGUgdHJhaW5pbmcgZGF0YQ0KbW9kZWwgPC0gZ2xtKGhhc19jYXJkIH4gLiwgZGF0YSA9IHRyYWluLCBmYW1pbHkgPSBiaW5vbWlhbCkNCmBgYA0KDQpgYGB7cn0NCiMgTWFrZSBwcmVkaWN0aW9ucyBvbiB0aGUgdGVzdCBkYXRhDQpwcmVkaWN0aW9ucyA8LSBwcmVkaWN0KG1vZGVsLCB0ZXN0LCB0eXBlID0gInJlc3BvbnNlIikNCg0KdGhyZXNob2xkIDwtIDAuNQ0KcHJlZGljdGlvbnNfdGhyZXNob2xkIDwtIGlmZWxzZShwcmVkaWN0aW9ucyA+IHRocmVzaG9sZCwgVFJVRSwgRkFMU0UpDQoNCmdldF9tZXRyaWNzKHByZWRpY3Rpb25zX3RocmVzaG9sZCwgdGVzdCRoYXNfY2FyZCkNCmBgYA0KDQpgYGB7cn0NCnBsb3RfY29uZnVzaW9uX21hdHJpeChwcmVkaWN0aW9uc190aHJlc2hvbGQsIHRlc3QkaGFzX2NhcmQpDQpgYGANCg0KYGBge3J9DQptYWtlX3JvY19wbG90X2FuZF9nZXRfYXVjKHByZWRpY3Rpb25zX3RocmVzaG9sZCwgdGVzdCRoYXNfY2FyZCkNCmBgYA0KDQoNCmBgYHtyfQ0KcGxvdF9mZWF0dXJlX2ltcG9ydGFuY2UobW9kZWwpDQpgYGANCg0KYGBge3J9DQpmaW5kX2Jlc3RfdGhyZXNob2xkKHByZWRpY3Rpb25zLCB0ZXN0JGhhc19jYXJkKQ0KYGBgDQoNCiMjIE1pdCBudXIgZGVuIGJlaWRlbiB3aWNodGlnc3RlbiBWYXJpYWJlbG4NCg0KYGBge3J9DQpmaW5hbF9kZl9zaW1wbGlmaWVkX3R3b192YXJpYWJsZXMgPC0gZmluYWxfZGZfc2ltcGxpZmllZCAlPiUgc2VsZWN0KG1heF9kaWZmZXJlbmNlXzEyLCBtYXhfZGlmZmVyZW5jZV8xMSkNCm1vZGVsIDwtIGdsbShoYXNfY2FyZCB+IC4sIGRhdGEgPSB0cmFpbiwgZmFtaWx5ID0gYmlub21pYWwpDQpwcmVkaWN0aW9ucyA8LSBwcmVkaWN0KG1vZGVsLCB0ZXN0LCB0eXBlID0gInJlc3BvbnNlIikNCg0KdGhyZXNob2xkIDwtIDAuNQ0KcHJlZGljdGlvbnNfdGhyZXNob2xkIDwtIGlmZWxzZShwcmVkaWN0aW9ucyA+IHRocmVzaG9sZCwgVFJVRSwgRkFMU0UpDQoNCmdldF9tZXRyaWNzKHByZWRpY3Rpb25zX3RocmVzaG9sZCwgdGVzdCRoYXNfY2FyZCkNCmBgYA0KDQoNCg0KIyBEZWNpc2lvbiBUcmVlDQoNCmBgYHtyfQ0KDQptb2RlbCA8LSBycGFydChoYXNfY2FyZCB+IC4sIGRhdGEgPSB0cmFpbiwgbWV0aG9kID0gImNsYXNzIikNCg0KIyBNYWtlIHByZWRpY3Rpb25zIG9uIHRoZSB0ZXN0IGRhdGEgKHNlY29uZCBjb2x1bW4gaXMgZm9yIHByb2JhYmlsaXR5IG9mIHRydWUpDQpwcmVkaWN0aW9ucyA8LSBwcmVkaWN0KG1vZGVsLCB0ZXN0LCB0eXBlID0gInByb2IiKVssIDJdDQoNCnRocmVzaG9sZCA8LSAwLjUNCnByZWRpY3Rpb25zX3RocmVzaG9sZCA8LSBpZmVsc2UocHJlZGljdGlvbnMgPiB0aHJlc2hvbGQsIFRSVUUsIEZBTFNFKQ0KDQpnZXRfbWV0cmljcyhwcmVkaWN0aW9uc190aHJlc2hvbGQsIHRlc3QkaGFzX2NhcmQpDQpgYGANCg0KYGBge3J9DQpwbG90X2NvbmZ1c2lvbl9tYXRyaXgocHJlZGljdGlvbnNfdGhyZXNob2xkLCB0ZXN0JGhhc19jYXJkKQ0KbWFrZV9yb2NfcGxvdF9hbmRfZ2V0X2F1YyhwcmVkaWN0aW9uc190aHJlc2hvbGQsIHRlc3QkaGFzX2NhcmQpDQpmaW5kX2Jlc3RfdGhyZXNob2xkKHByZWRpY3Rpb25zLCB0ZXN0JGhhc19jYXJkKQ0KcnBhcnQucGxvdChtb2RlbCkNCmBgYA0KDQoNCkRpZSBLbGFzc2lmaWthdGlvbiBpc3QgdW0gZWluaWdlcyBiZXNzZXIgYXVmIGRlbSBEZWNpc2lvbiBUcmVlLiBFcyBzY2hlaW50IGFsc28gZsO8ciB1bnNlcmVuIFZlcndlbmR1bmdzendlY2sgZGVyIGJlc3NlcmUgQWxnb3JpdGhtdXMgenUgc2Vpbi4gV2lyIHVudGVyc3VjaGVuIG5vY2ggRXJ3ZWl0ZXJ1bmdlbiBkZXMgRGVjaXNpb24gVHJlZXM6IGRlciBSYW5kb20gRm9yZXN0Lg0KDQoNCg0KIyBSYW5kb20gRm9yZXN0IA0KDQpCZWltIFJhbmRvbSBGb3Jlc3QgbXVzcyB1bnNlcmUgWmllbHZhcmlhYmVsIG5vY2ggaW4gZWluZW4gRmFrdG9yIHVtZ2V3YW5kZWx0IHdlcmRlbiwgZGFtaXQgZGllIFdhaHJzY2hlaW5saWNoa2VpdGVuIHZvcmhlcmdlc2FndCB3ZXJkZW4ga8O2bm5lbi4NCg0KYGBge3J9DQp0cmFpbiRoYXNfY2FyZCA8LSBhcy5mYWN0b3IodHJhaW4kaGFzX2NhcmQpDQojIEZpdCBhIHJhbmRvbSBmb3Jlc3QgbW9kZWwgdG8gdGhlIHRyYWluaW5nIGRhdGENCm1vZGVsIDwtIHJhbmRvbUZvcmVzdChoYXNfY2FyZCB+IC4sIGRhdGEgPSB0cmFpbiwgbWV0aG9kID0gImNsYXNzIikNCg0KIyBNYWtlIHByZWRpY3Rpb25zIG9uIHRoZSB0ZXN0IGRhdGEgKHNlY29uZCBjb2x1bW4gaXMgZm9yIHByb2JhYmlsaXR5IG9mIHRydWUpDQpwcmVkaWN0aW9ucyA8LSBwcmVkaWN0KG1vZGVsLCB0ZXN0LCB0eXBlID0gInByb2IiKVssIDJdDQoNCiMgVGhyZXNob2xkIHRoZSBwcmVkaWN0ZWQgcHJvYmFiaWxpdGllcw0KdGhyZXNob2xkIDwtIDAuNQ0KcHJlZGljdGlvbnNfdGhyZXNob2xkIDwtIGlmZWxzZShwcmVkaWN0aW9ucyA+IHRocmVzaG9sZCwgVFJVRSwgRkFMU0UpDQoNCiMgRXZhbHVhdGUgdGhlIG1vZGVsJ3MgcGVyZm9ybWFuY2UNCmdldF9tZXRyaWNzKHByZWRpY3Rpb25zX3RocmVzaG9sZCwgdGVzdCRoYXNfY2FyZCkNCmBgYA0KDQpEZXIgUmFuZG9tIEZvcmVzdCBpc3QgZWluIHdlbmlnIGJlc3NlciBhbHMgZGVyIERlY2lzaW9uIFRyZWUuIEFscyBuw6RjaHN0ZXMgd29sbGVuIHdpciBwcm9iaWVyZW4sIG9iIGVpbmUgSHlwZXJwYXJhbWV0ZXJvcHRpbWllcnVuZyB1bnNlciBSZXN1bHRhdCBub2NoIHZlcmJlc3Nlcm4ga2Fubi4NCg0KDQpgYGB7cn0NCnBsb3RfY29uZnVzaW9uX21hdHJpeChwcmVkaWN0aW9uc190aHJlc2hvbGQsIHRlc3QkaGFzX2NhcmQpDQptYWtlX3JvY19wbG90X2FuZF9nZXRfYXVjKHByZWRpY3Rpb25zX3RocmVzaG9sZCwgdGVzdCRoYXNfY2FyZCkNCmZpbmRfYmVzdF90aHJlc2hvbGQocHJlZGljdGlvbnMsIHRlc3QkaGFzX2NhcmQpDQpgYGANCg0KYGBge3J9DQppbXBvcnRhbmNlX3ZhbHVlcyA8LSB2YXJJbXAobW9kZWwpDQppbXBvcnRhbmNlX3ZhbHVlcyR2YXJpYWJsZSA8LSByb3duYW1lcyh2YXJJbXAobW9kZWwpKSANCmltcG9ydGFuY2VfdmFsdWVzIDwtIGltcG9ydGFuY2VfdmFsdWVzW29yZGVyKGltcG9ydGFuY2VfdmFsdWVzJE92ZXJhbGwsIGRlY3JlYXNpbmcgPSBUUlVFKSxdDQoNCiMgV8OkaGxlbiBTaWUgZGllIHRvcCAxMCBNZXJrbWFsZSBhdXMNCnRvcF9mZWF0dXJlcyA8LSBoZWFkKGltcG9ydGFuY2VfdmFsdWVzLCBuID0gMTApDQp0b3BfZmVhdHVyZXMNCg0KYGBgDQoNCmBgYHtyfQ0KIyBJbml0aWFsaXplIGVtcHR5IHZlY3RvciB0byBzdG9yZSBmMS1zY29yZXMNCmYxX3Njb3JlcyA8LSBjKCkNCg0KIyBMb29wIHRocm91Z2ggZWFjaCB2YXJpYWJsZSBhbmQgZml0IGEgcmFuZG9tIGZvcmVzdCBtb2RlbA0KZm9yIChpIGluIDE6NTApIHsNCiAgIyBTZWxlY3Qgc3Vic2V0IG9mIHZhcmlhYmxlcw0KICB2YXJzX3N1YnNldCA8LSBpbXBvcnRhbmNlX3ZhbHVlcyR2YXJpYWJsZVsxOmldDQogIA0KICAjIEZpdCByYW5kb20gZm9yZXN0IG1vZGVsDQogIHRyYWluX3JlZHVjZWQgPC0gdHJhaW4gJT4lIHNlbGVjdChoYXNfY2FyZCwgdmFyc19zdWJzZXQpDQogIG1vZGVsIDwtIHJhbmRvbUZvcmVzdChoYXNfY2FyZCB+IC4sIGRhdGEgPSB0cmFpbl9yZWR1Y2VkLCBtZXRob2QgPSAiY2xhc3MiKQ0KICANCiAgIyBNYWtlIHByZWRpY3Rpb25zIG9uIHRlc3QgZGF0YQ0KICBwcmVkaWN0aW9ucyA8LSBwcmVkaWN0KG1vZGVsLCB0ZXN0LCB0eXBlID0gInByb2IiKVssIDJdDQogIA0KICAjIFRocmVzaG9sZCBwcmVkaWN0aW9ucw0KICB0aHJlc2hvbGQgPC0gMC41DQogIHByZWRpY3Rpb25zX3RocmVzaG9sZCA8LSBpZmVsc2UocHJlZGljdGlvbnMgPiB0aHJlc2hvbGQsIFRSVUUsIEZBTFNFKQ0KICANCiAgIyBDYWxjdWxhdGUgZjEtc2NvcmUNCiAgZjEgPC0gZ2V0X21ldHJpY3MocHJlZGljdGlvbnNfdGhyZXNob2xkLCB0ZXN0JGhhc19jYXJkKSRmMQ0KICANCiAgIyBBcHBlbmQgZjEtc2NvcmUgdG8gdmVjdG9yDQogIGYxX3Njb3JlcyA8LSBjKGYxX3Njb3JlcywgZjEpDQp9DQoNCiMgRmluZCB0aGUgaW5kZXggb2YgdGhlIG1heGltdW0gZjEtc2NvcmUNCmJlc3RfaW5kZXggPC0gd2hpY2gubWF4KGYxX3Njb3JlcykNCg0KIyBQcmludCB0aGUgYmVzdCBmMS1zY29yZSBhbmQgdGhlIGNvcnJlc3BvbmRpbmcgbnVtYmVyIG9mIHZhcmlhYmxlcw0KcHJpbnQocGFzdGUoIkJlc3QgZjEtc2NvcmU6IiwgZjFfc2NvcmVzW2Jlc3RfaW5kZXhdKSkNCnByaW50KHBhc3RlKCJOdW1iZXIgb2YgdmFyaWFibGVzOiIsIGJlc3RfaW5kZXgpKQ0KDQojIFBsb3QgdGhlIGYxLXNjb3Jlcw0KcGxvdCgxOmxlbmd0aChmMV9zY29yZXMpLCBmMV9zY29yZXMsIHR5cGUgPSAibCIsIHhsYWIgPSAiTnVtYmVyIG9mIHZhcmlhYmxlcyIsIHlsYWIgPSAiZjEtc2NvcmUiKQ0KYGBgDQoNCmBgYHtyfQ0Kc2V0LnNlZWQoMjcpDQojIEZpdCBhIHJhbmRvbSBmb3Jlc3QgbW9kZWwgdG8gdGhlIHRyYWluaW5nIGRhdGENCm1vZGVsIDwtIHJhbmRvbUZvcmVzdChoYXNfY2FyZCB+IC4sIGRhdGEgPSB0cmFpbiAlPiUgc2VsZWN0KGhhc19jYXJkLCBpbXBvcnRhbmNlX3ZhbHVlcyR2YXJpYWJsZVsxOjddKSwgbWV0aG9kID0gImNsYXNzIikNCg0KIyBNYWtlIHByZWRpY3Rpb25zIG9uIHRoZSB0ZXN0IGRhdGEgKHNlY29uZCBjb2x1bW4gaXMgZm9yIHByb2JhYmlsaXR5IG9mIHRydWUpDQpwcmVkaWN0aW9ucyA8LSBwcmVkaWN0KG1vZGVsLCB0ZXN0LCB0eXBlID0gInByb2IiKVssIDJdDQoNCiMgVGhyZXNob2xkIHRoZSBwcmVkaWN0ZWQgcHJvYmFiaWxpdGllcw0KdGhyZXNob2xkIDwtIDAuNQ0KcHJlZGljdGlvbnNfdGhyZXNob2xkIDwtIGlmZWxzZShwcmVkaWN0aW9ucyA+IHRocmVzaG9sZCwgVFJVRSwgRkFMU0UpDQoNCiMgRXZhbHVhdGUgdGhlIG1vZGVsJ3MgcGVyZm9ybWFuY2UNCmdldF9tZXRyaWNzKHByZWRpY3Rpb25zX3RocmVzaG9sZCwgdGVzdCRoYXNfY2FyZCkNCmBgYA0KDQoNCg0KIyBIeXBlcnBhcmFtdGVyIE9wdGltaWVydW5nIGF1ZiBSYW5kb20gRm9yZXN0DQoNCmBgYHtyfQ0Kc2V0LnNlZWQoMjcpDQojIERlZmluZSB0aGUgaHlwZXJwYXJhbWV0ZXIgZ3JpZA0KcGFyYW1fZ3JpZCA8LSBleHBhbmQuZ3JpZChtdHJ5ID0gYygwLjEsIDEsIDEwLCAxMDApLA0KICAgICAgICAgICAgICAgICAgICAgICAgIHNwbGl0cnVsZSA9IGMoImdpbmkiLCAiZXh0cmF0cmVlcyIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgIG1pbi5ub2RlLnNpemUgPSBjKDEsIDEwLCAxNSwgMjApLA0KICAgICAgICAgICAgICAgICAgICAgICAgIG1heC5kZXB0aCA9IHNlcSgxLCAzMCwgNSksDQogICAgICAgICAgICAgICAgICAgICAgICAgbnRyZWUgPSBjKDEwMCwgNTAwLCAxMDAwKSkNCg0KIyBEZWZpbmUgdGhlIG1vZGVsIHVzaW5nIHRoZSB0cmFpbigpIGZ1bmN0aW9uDQptb2RlbCA8LSByYW5kb21Gb3Jlc3QoaGFzX2NhcmQgfiAuLA0KICAgICAgICAgICAgICAgZGF0YSA9IHRyYWluICU+JSBzZWxlY3QoaGFzX2NhcmQsIGltcG9ydGFuY2VfdmFsdWVzJHZhcmlhYmxlWzE6N10pLA0KICAgICAgICAgICAgICAgbWV0aG9kID0gInJmIiwNCiAgICAgICAgICAgICAgIHR1bmVHcmlkID0gcGFyYW1fZ3JpZCwNCiAgICAgICAgICAgICAgIHRyQ29udHJvbCA9IHRyYWluQ29udHJvbChtZXRob2QgPSAiY3YiLCBudW1iZXIgPSA1KSkNCg0KIyBNYWtlIHByZWRpY3Rpb25zIG9uIHRoZSB0ZXN0IGRhdGENCnByZWRpY3Rpb25zIDwtIHByZWRpY3QobW9kZWwsIHRlc3QsIHR5cGUgPSAicHJvYiIpWywgMl0NCg0KIyBUaHJlc2hvbGQgdGhlIHByZWRpY3RlZCBwcm9iYWJpbGl0aWVzDQp0aHJlc2hvbGQgPC0gMC41DQpwcmVkaWN0aW9uc190aHJlc2hvbGQgPC0gaWZlbHNlKHByZWRpY3Rpb25zID4gdGhyZXNob2xkLCBUUlVFLCBGQUxTRSkNCg0KcHJpbnQobW9kZWwpDQoNCiMgRXZhbHVhdGUgdGhlIG1vZGVsJ3MgcGVyZm9ybWFuY2UNCmdldF9tZXRyaWNzKHByZWRpY3Rpb25zX3RocmVzaG9sZCwgdGVzdCRoYXNfY2FyZCkNCmBgYA0KDQoNCg0KIyBSZWN1cnNpdmUgRmVhdHVyZSBFbGltbmF0aW9uDQoNCkVpbmUgTcO2Z2xpY2hrZWl0LCBkaWUgTGVpc3R1bmcgdm9uIFJhbmRvbSBGb3Jlc3QgenUgdmVyYmVzc2VybiwgaXN0IGRpZSBWZXJ3ZW5kdW5nIHZvbiBSZWN1cnNpdmUgRmVhdHVyZSBFbGltaW5hdGlvbiAoUkZFKS4NCg0KUkZFIGlzdCBlaW4gRmVhdHVyZSBTZWxlY3Rpb24tVmVyZmFocmVuLCBkYXMgZGF6dSB2ZXJ3ZW5kZXQgd2lyZCwgZGllIHdpY2h0aWdzdGVuIEZlYXR1cmVzIChhbHNvIGRpZWplbmlnZW4gTWVya21hbGUsIGRpZSBmw7xyIGRpZSBWb3JoZXJzYWdlIGFtIHdpY2h0aWdzdGVuIHNpbmQpIGF1c3p1d8OkaGxlbiB1bmQgYWxsZSBhbmRlcmVuIHp1IGVudGZlcm5lbi4gRGllcyBoYXQgbWVocmVyZSBWb3J0ZWlsZToNCg0KRXMgcmVkdXppZXJ0IGRpZSBMYXVmemVpdCB2b24gUmFuZG9tIEZvcmVzdCwgZGEgd2VuaWdlciBGZWF0dXJlcyB2ZXJhcmJlaXRldCB3ZXJkZW4gbcO8c3Nlbi4NCkVzIGthbm4gZGF6dSBiZWl0cmFnZW4sIE92ZXJmaXR0aW5nIHp1IHZlcm1laWRlbiwgaW5kZW0gZXMgaXJyZWxldmFudGVuIG9kZXIgcmVkdW5kYW50ZW4gRmVhdHVyZXMgZW50ZmVybnQuDQpFcyBrYW5uIGRhenUgYmVpdHJhZ2VuLCBkaWUgSW50ZXJwcmV0aWVyYmFya2VpdCB2b24gUmFuZG9tIEZvcmVzdCB6dSB2ZXJiZXNzZXJuLCBkYSB3aWNodGlnZXJlIEZlYXR1cmVzIGxlaWNodGVyIHp1IHZlcnN0ZWhlbiBzaW5kLg0KDQoNCmBgYHtyfQ0KaWYgKEZBTFNFKSB7DQojIERlZmluZSB0aGUgY29udHJvbCBvYmplY3QNCmNvbnRyb2wgPC0gcmZlQ29udHJvbChmdW5jdGlvbnMgPSByZkZ1bmNzLA0KICAgICAgICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJyZXBlYXRlZGN2IiwNCiAgICAgICAgICAgICAgICAgICAgICByZXBlYXRzID0gNSkNCg0KIyBQZXJmb3JtIFJGRQ0KbW9kZWwgPC0gcmZlKHggPSB0cmFpbiAlPiUgc2VsZWN0KC1oYXNfY2FyZCksIHkgPSB0cmFpbiRoYXNfY2FyZCwNCiAgICAgICAgICAgICBzaXplcyA9IGMoMToxOSwgc2VxKGZyb20gPSAyMCwgdG8gPSBuY29sKHRyYWluKSwgYnkgPSAxMCkpLA0KICAgICAgICAgICAgIHJmZUNvbnRyb2wgPSBjb250cm9sKQ0KDQojIEV4dHJhY3QgdGhlIHNlbGVjdGVkIGZlYXR1cmVzDQpzZWxlY3RlZF9mZWF0dXJlcyA8LSBtb2RlbCRvcHRWYXJpYWJsZXMNCg0KcHJpbnQobW9kZWwpDQp9DQpgYGANCg0KYGBge3J9DQojIEZpdCBhIHJhbmRvbSBmb3Jlc3QgbW9kZWwgdXNpbmcgdGhlIHNlbGVjdGVkIGZlYXR1cmVzDQptb2RlbCA8LSByYW5kb21Gb3Jlc3QoaGFzX2NhcmQgfiAuLCBkYXRhID0gdHJhaW5bYyhzZWxlY3RlZF9mZWF0dXJlcywgImhhc19jYXJkIildLCBtZXRob2QgPSAiY2xhc3MiKQ0KDQojIE1ha2UgcHJlZGljdGlvbnMgb24gdGhlIHRlc3QgZGF0YQ0KcHJlZGljdGlvbnMgPC0gcHJlZGljdChtb2RlbCwgdGVzdCwgdHlwZSA9ICJwcm9iIilbLCAyXQ0KDQojIFRocmVzaG9sZCB0aGUgcHJlZGljdGVkIHByb2JhYmlsaXRpZXMNCnRocmVzaG9sZCA8LSAwLjUNCnByZWRpY3Rpb25zX3RocmVzaG9sZCA8LSBpZmVsc2UocHJlZGljdGlvbnMgPiB0aHJlc2hvbGQsIFRSVUUsIEZBTFNFKQ0KDQojIEV2YWx1YXRlIHRoZSBtb2RlbCdzIHBlcmZvcm1hbmNlDQpnZXRfbWV0cmljcyhwcmVkaWN0aW9uc190aHJlc2hvbGQsIHRlc3QkaGFzX2NhcmQpDQpgYGANCg0KDQoNCg==